diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 003e504c92..28d78989f8 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -326,6 +326,7 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_ssl ssl/qsslkey.h ssl/qsslkey_p.cpp ssl/qsslkey_p.h ssl/qsslpresharedkeyauthenticator.cpp ssl/qsslpresharedkeyauthenticator.h ssl/qsslpresharedkeyauthenticator_p.h ssl/qsslsocket.cpp ssl/qsslsocket.h ssl/qsslsocket_p.h + ssl/qtlsbackend.cpp ssl/qtlsbackend_p.h ) qt_internal_extend_target(Network CONDITION QT_FEATURE_schannel AND QT_FEATURE_ssl diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 644e3771dd..0c231c1600 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -385,6 +385,7 @@ #include "qsslsocket.h" #include "qsslcipher.h" #include "qocspresponse.h" +#include "qtlsbackend_p.h" #ifndef QT_NO_OPENSSL #include "qsslsocket_openssl_p.h" #endif @@ -1556,43 +1557,68 @@ QString QSslSocket::sslLibraryBuildVersionString() */ QList QSslSocket::availableBackends() { - return QSslSocketPrivate::availableBackends(); + return QTlsBackendFactory::availableBackendNames(); } /*! \since 6.1 - Returns the name of the backend that was loaded (implicitly by QSslSocket - or by an application via loadBackend() call). If no backend was loaded yet, - this function returns the name of the backend that will be loaded by QSslSocket. + Returns the name of the backend that QSslSocket and related classes + use. If the active backend was not set explicitly, this function + returns the name of a default backend that QSslSocket selects implicitly + from the list of available backends. - \note When selecting a default backend implicitly from the list of available - backends, QSslSocket prefers native backends, such as SecureTransport on Darwin, - or Schannel on Windows. + \note When selecting a default backend implicitly, QSslSocket prefers + native backends, such as SecureTransport on Darwin, or Schannel on Windows. - \sa loadBackend(), availableBackends() + \sa setActiveBackend(), availableBackends() */ QString QSslSocket::activeBackend() { - return QSslSocketPrivate::activeBackend(); + const QMutexLocker locker(&QSslSocketPrivate::backendMutex); + + if (!QSslSocketPrivate::activeBackendName.size()) + QSslSocketPrivate::activeBackendName = QTlsBackendFactory::defaultBackendName(); + + return QSslSocketPrivate::activeBackendName; } /*! \since 6.1 - Returns true if a backend with name \a backendName was loaded - and was made the current active backend. \a backendName must - be one of names returned by availableBackends(). + Returns true if a backend with name \a backendName was set as + active backend. \a backendName must be one of names returned + by availableBackends(). - \note An application can switch from the default backend, - that will be implicitly loaded by QSslSocket, to a different backend - only once. It cannot mix several backends simultaneously. A non-default - backend must be selected prior to any use of QSslSocket or related classes - (like QSslCertificate or QSslKey). + \note An application cannot mix different backends simultaneously. + This implies that a non-default backend must be selected prior + to any use of QSslSocket or related classes, e.g. QSslCertificate + or QSslKey. \sa activeBackend(), availableBackends() */ -bool QSslSocket::loadBackend(const QString &backendName) +bool QSslSocket::setActiveBackend(const QString &backendName) { - return QSslSocketPrivate::loadBackend(backendName); + if (!backendName.size()) { + qCWarning(lcSsl, "Invalid parameter (backend name cannot be an empty string)"); + return false; + } + + QMutexLocker locker(&QSslSocketPrivate::backendMutex); + if (QSslSocketPrivate::tlsBackend.get()) { + qCWarning(lcSsl) << "Cannot set backend named" << backendName + << "as active, another backend is already in use"; + locker.unlock(); + return activeBackend() == backendName; + } + + if (!QTlsBackendFactory::availableBackendNames().contains(backendName)) { + qCWarning(lcSsl) << "Cannot set unavailable backend named" << backendName + << "as active"; + return false; + } + + QSslSocketPrivate::activeBackendName = backendName; + + return true; } /*! @@ -1606,13 +1632,7 @@ bool QSslSocket::loadBackend(const QString &backendName) */ QList QSslSocket::supportedProtocols(const QString &backendName) { - if (Q_UNLIKELY(backendName.size() && !availableBackends().contains(backendName))) { - qCWarning(lcSsl) << "Cannot provide the list of supported protocols for the backend" - << backendName; - return {}; - } - - return QSslSocketPrivate::supportedProtocols(backendName); + return QTlsBackendFactory::supportedProtocols(backendName.size() ? backendName : activeBackend()); } /*! @@ -1638,13 +1658,7 @@ bool QSslSocket::isProtocolSupported(QSsl::SslProtocol protocol, const QString & */ QList QSslSocket::implementedClasses(const QString &backendName) { - if (Q_UNLIKELY(backendName.size() && !availableBackends().contains(backendName))) { - qCWarning(lcSsl) << "Cannot provide information about supported classes for" - << "backend" << backendName; - return {}; - } - - return QSslSocketPrivate::implementedClasses(backendName); + return QTlsBackendFactory::implementedClasses(backendName.size() ? backendName : activeBackend()); } /*! @@ -1669,13 +1683,7 @@ bool QSslSocket::isClassImplemented(QSsl::ImplementedClass cl, const QString &ba */ QList QSslSocket::supportedFeatures(const QString &backendName) { - if (Q_UNLIKELY(backendName.size() && !availableBackends().contains(backendName))) { - qCWarning(lcSsl) << "Cannot provide information about supported features for" - << "backend" << backendName; - return {}; - } - - return QSslSocketPrivate::supportedFeatures(backendName); + return QTlsBackendFactory::supportedFeatures(backendName.size() ? backendName : activeBackend()); } /*! diff --git a/src/network/ssl/qsslsocket.h b/src/network/ssl/qsslsocket.h index 9bc90fad32..8841929eec 100644 --- a/src/network/ssl/qsslsocket.h +++ b/src/network/ssl/qsslsocket.h @@ -165,7 +165,7 @@ public: static QList availableBackends(); static QString activeBackend(); - static bool loadBackend(const QString &backendName); + static bool setActiveBackend(const QString &backendName); static QList supportedProtocols(const QString &backendName = {}); static bool isProtocolSupported(QSsl::SslProtocol protocol, const QString &backendName = {}); static QList implementedClasses(const QString &backendName = {}); diff --git a/src/network/ssl/qsslsocket_mac.cpp b/src/network/ssl/qsslsocket_mac.cpp index 9f1d8d1e54..abbcf8a6ac 100644 --- a/src/network/ssl/qsslsocket_mac.cpp +++ b/src/network/ssl/qsslsocket_mac.cpp @@ -44,6 +44,7 @@ #include "qsslsocket_mac_p.h" #include "qasn1element_p.h" #include "qsslcertificate_p.h" +#include "qtlsbackend_p.h" #include "qsslcipher_p.h" #include "qsslkey_p.h" @@ -75,6 +76,67 @@ QT_BEGIN_NAMESPACE namespace { + +// These two classes are ad-hoc temporary solution, to be replaced +// by the real things soon. +class SecureTransportBackend : public QTlsBackend +{ +private: + QString backendName() const override + { + return QTlsBackendFactory::builtinBackendNames[QTlsBackendFactory::nameIndexSecureTransport]; + } +}; + +class SecureTransportBackendFactory : public QTlsBackendFactory +{ +private: + QString backendName() const override + { + return QTlsBackendFactory::builtinBackendNames[QTlsBackendFactory::nameIndexSecureTransport]; + } + QTlsBackend *create() const override + { + return new SecureTransportBackend; + } + + QList supportedProtocols() const override + { + QList protocols; + + protocols << QSsl::AnyProtocol; + protocols << QSsl::SecureProtocols; + protocols << QSsl::TlsV1_0; + protocols << QSsl::TlsV1_0OrLater; + protocols << QSsl::TlsV1_1; + protocols << QSsl::TlsV1_1OrLater; + protocols << QSsl::TlsV1_2; + protocols << QSsl::TlsV1_2OrLater; + + return protocols; + } + + QList supportedFeatures() const override + { + QList features; + features << QSsl::SupportedFeature::ClientSideAlpn; + + return features; + } + + QList implementedClasses() const override + { + QList classes; + classes << QSsl::ImplementedClass::Socket; + classes << QSsl::ImplementedClass::Certificate; + classes << QSsl::ImplementedClass::Key; + + return classes; + } +}; + +Q_GLOBAL_STATIC(SecureTransportBackendFactory, factory) + #ifdef Q_OS_MACOS /* @@ -1552,90 +1614,12 @@ bool QSslSocketBackendPrivate::startHandshake() } } -QList QSslSocketPrivate::availableBackends() +void QSslSocketPrivate::registerAdHocFactory() { - return {QStringLiteral("securetransport")}; -} - -QString QSslSocketPrivate::activeBackend() -{ - return availableBackends().first(); -} - -bool QSslSocketPrivate::loadBackend(const QString &backendName) -{ - if (backendName.size() && !availableBackends().contains(backendName)) { - qCWarning(lcSsl) << "A TLS backend with name" << backendName << "is not available"; - return false; - } - - static bool loaded = false; - static QBasicMutex mutex; - const QMutexLocker locker(&mutex); - if (loaded) { - qCWarning(lcSsl) << "You have already loaded the backend named:" << activeBackend(); - if (backendName.size()) - qCWarning(lcSsl) << "Cannot load:" << backendName; - else - qCWarning(lcSsl) << "Cannot load the default backend (securetransport)"; - return true; - } - // This code to be placed in qsslsocket.cpp and there - // the actual plugin to be loaded (so the result can be - // false if we, for example, failed to resolve OpenSSL - // symbols). - return loaded = true; -} - -QList QSslSocketPrivate::supportedProtocols(const QString &backendName) -{ - QList protocols; - if (backendName.size() && backendName != activeBackend()) { - qCWarning(lcSsl) << "Unexpected backend name" << backendName - << "no information about protocols supported can be found"; - return protocols; - } - - protocols << QSsl::AnyProtocol; - protocols << QSsl::SecureProtocols; - protocols << QSsl::TlsV1_0; - protocols << QSsl::TlsV1_0OrLater; - protocols << QSsl::TlsV1_1; - protocols << QSsl::TlsV1_1OrLater; - protocols << QSsl::TlsV1_2; - protocols << QSsl::TlsV1_2OrLater; - - return protocols; -} - -QList QSslSocketPrivate::implementedClasses(const QString &backendName) -{ - QList classes; - if (backendName.size() && backendName != activeBackend()) { - qCWarning(lcSsl) << "Unexpected backend name" << backendName - << "no information about classes implemented can be found"; - return classes; - } - - classes << QSsl::ImplementedClass::Key; - classes << QSsl::ImplementedClass::Certificate; - classes << QSsl::ImplementedClass::Socket; - - return classes; -} - -QList QSslSocketPrivate::supportedFeatures(const QString &backendName) -{ - QList features; - if (backendName.size() && backendName != activeBackend()) { - qCWarning(lcSsl) << "Unexpected backend name" << backendName - << "no information about classes implemented can be found"; - return features; - } - - features << QSsl::SupportedFeature::ClientSideAlpn; - - return features; + // TLSTODO: this is a temporary solution, waiting for + // backends to move to ... plugins. + if (!factory()) + qCWarning(lcSsl, "Failed to create backend factory"); } QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp index cab18d3147..2f39b68002 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -101,6 +101,89 @@ QT_BEGIN_NAMESPACE namespace { +// These two classes are ad-hoc temporary solution, to be replaced +// by the real things soon. +class OpenSSLBackend : public QTlsBackend +{ +private: + QString backendName() const override + { + return QTlsBackendFactory::builtinBackendNames[QTlsBackendFactory::nameIndexOpenSSL]; + } +}; + +class OpenSSLBackendFactory : public QTlsBackendFactory +{ +private: + QString backendName() const override + { + return QTlsBackendFactory::builtinBackendNames[QTlsBackendFactory::nameIndexOpenSSL]; + } + QTlsBackend *create() const override + { + return new OpenSSLBackend; + } + + QList supportedProtocols() const override + { + QList protocols; + + protocols << QSsl::AnyProtocol; + protocols << QSsl::SecureProtocols; + protocols << QSsl::TlsV1_0; + protocols << QSsl::TlsV1_0OrLater; + protocols << QSsl::TlsV1_1; + protocols << QSsl::TlsV1_1OrLater; + protocols << QSsl::TlsV1_2; + protocols << QSsl::TlsV1_2OrLater; + +#ifdef TLS1_3_VERSION + protocols << QSsl::TlsV1_3; + protocols << QSsl::TlsV1_3OrLater; +#endif // TLS1_3_VERSION + +#if QT_CONFIG(dtls) + protocols << QSsl::DtlsV1_0; + protocols << QSsl::DtlsV1_0OrLater; + protocols << QSsl::DtlsV1_2; + protocols << QSsl::DtlsV1_2OrLater; +#endif // dtls + + return protocols; + } + + QList supportedFeatures() const override + { + QList features; + + features << QSsl::SupportedFeature::CertificateVerification; + features << QSsl::SupportedFeature::ClientSideAlpn; + features << QSsl::SupportedFeature::ServerSideAlpn; + features << QSsl::SupportedFeature::Ocsp; + features << QSsl::SupportedFeature::Psk; + features << QSsl::SupportedFeature::SessionTicket; + features << QSsl::SupportedFeature::Alerts; + + return features; + } + + QList implementedClasses() const override + { + QList classes; + + classes << QSsl::ImplementedClass::Key; + classes << QSsl::ImplementedClass::Certificate; + classes << QSsl::ImplementedClass::Socket; + classes << QSsl::ImplementedClass::Dtls; + classes << QSsl::ImplementedClass::EllipticCurve; + classes << QSsl::ImplementedClass::DiffieHellman; + + return classes; + } +}; + +Q_GLOBAL_STATIC(OpenSSLBackendFactory, factory) + QSsl::AlertLevel tlsAlertLevel(int value) { using QSsl::AlertLevel; @@ -2510,111 +2593,12 @@ bool QSslSocketBackendPrivate::importPkcs12(QIODevice *device, return true; } -QList QSslSocketPrivate::availableBackends() +void QSslSocketPrivate::registerAdHocFactory() { - return {QStringLiteral("openssl")}; -} - -QString QSslSocketPrivate::activeBackend() -{ - return availableBackends().first(); -} - -bool QSslSocketPrivate::loadBackend(const QString &backendName) -{ - if (backendName.size() && backendName != activeBackend()) { - qCWarning(lcSsl) << "A TLS backend with name" << backendName << "is not available"; - return false; - } - - static bool loaded = false; - static QBasicMutex mutex; - const QMutexLocker locker(&mutex); - if (loaded) { - qCWarning(lcSsl) << "You have already loaded the backend named:" << activeBackend(); - if (backendName.size()) - qCWarning(lcSsl) << "Cannot load:" << backendName; - else - qCWarning(lcSsl) << "Cannot load the default backend (openssl)"; - return true; - } - // This code to be placed in qsslsocket.cpp and there - // the actual plugin to be loaded (so the result can be - // false if we, for example, failed to resolve OpenSSL - // symbols). - return loaded = true; -} - -QList QSslSocketPrivate::supportedProtocols(const QString &backendName) -{ - QList protocols; - if (backendName.size() && backendName != activeBackend()) { - qCWarning(lcSsl) << "Unexpected backend name" << backendName - << "no information about protocols supported can be found"; - return protocols; - } - - protocols << QSsl::AnyProtocol; - protocols << QSsl::SecureProtocols; - protocols << QSsl::TlsV1_0; - protocols << QSsl::TlsV1_0OrLater; - protocols << QSsl::TlsV1_1; - protocols << QSsl::TlsV1_1OrLater; - protocols << QSsl::TlsV1_2; - protocols << QSsl::TlsV1_2OrLater; - -#ifdef TLS1_3_VERSION - protocols << QSsl::TlsV1_3; - protocols << QSsl::TlsV1_3OrLater; -#endif // TLS1_3_VERSION - -#if QT_CONFIG(dtls) - protocols << QSsl::DtlsV1_0; - protocols << QSsl::DtlsV1_0OrLater; - protocols << QSsl::DtlsV1_2; - protocols << QSsl::DtlsV1_2OrLater; -#endif // dtls - - return protocols; -} - -QList QSslSocketPrivate::implementedClasses(const QString &backendName) -{ - QList classes; - if (backendName.size() && backendName != activeBackend()) { - qCWarning(lcSsl) << "Unexpected backend name" << backendName - << "no information about classes implemented can be found"; - return classes; - } - - classes << QSsl::ImplementedClass::Key; - classes << QSsl::ImplementedClass::Certificate; - classes << QSsl::ImplementedClass::Socket; - classes << QSsl::ImplementedClass::Dtls; - classes << QSsl::ImplementedClass::EllipticCurve; - classes << QSsl::ImplementedClass::DiffieHellman; - - return classes; -} - -QList QSslSocketPrivate::supportedFeatures(const QString &backendName) -{ - QList features; - if (backendName.size() && backendName != activeBackend()) { - qCWarning(lcSsl) << "Unexpected backend name" << backendName - << "no information about classes implemented can be found"; - return features; - } - - features << QSsl::SupportedFeature::CertificateVerification; - features << QSsl::SupportedFeature::ClientSideAlpn; - features << QSsl::SupportedFeature::ServerSideAlpn; - features << QSsl::SupportedFeature::Ocsp; - features << QSsl::SupportedFeature::Psk; - features << QSsl::SupportedFeature::SessionTicket; - features << QSsl::SupportedFeature::Alerts; - - return features; + // TLSTODO: this is a temporary solution, waiting for + // backends to move to ... plugins. + if (!factory()) + qCWarning(lcSsl, "Failed to create backend factory"); } QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h index a98f833b0c..0a30f02e5f 100644 --- a/src/network/ssl/qsslsocket_p.h +++ b/src/network/ssl/qsslsocket_p.h @@ -59,6 +59,7 @@ #include "qsslkey.h" #include "qsslconfiguration_p.h" #include "qocspresponse.h" +#include "qtlsbackend_p.h" #ifndef QT_NO_OPENSSL #include #else @@ -67,6 +68,8 @@ class QSslContext; #include #include +#include + #include #if defined(Q_OS_MAC) @@ -81,6 +84,8 @@ class QSslContext; #endif // !HCRYPTPROV_LEGACY #endif // Q_OS_WIN +#include + QT_BEGIN_NAMESPACE #if defined(Q_OS_MACOS) @@ -91,7 +96,7 @@ QT_BEGIN_NAMESPACE #if defined(Q_OS_WIN) -// Those are needed by both OpenSSL and SChannel back-ends on Windows: +// Those are needed by both OpenSSL and Schannel back-ends on Windows: struct QHCertStoreDeleter { void operator()(HCERTSTORE store) { @@ -204,12 +209,9 @@ public: Q_AUTOTEST_EXPORT static bool rootCertOnDemandLoadingSupported(); - static QList availableBackends(); - static QString activeBackend(); static bool loadBackend(const QString &backendName); - static QList supportedProtocols(const QString &backendName); - static QList implementedClasses(const QString &backendName); - static QList supportedFeatures(const QString &backendName); + static void registerAdHocFactory(); + private: static bool ensureLibraryLoaded(); static void ensureCiphersAndCertsLoaded(); @@ -228,6 +230,10 @@ protected: bool handshakeInterrupted = false; bool fetchAuthorityInformation = false; QSslCertificate caToFetch; + + static inline QMutex backendMutex; + static inline QString activeBackendName; + static inline std::unique_ptr tlsBackend; }; #if QT_CONFIG(securetransport) || QT_CONFIG(schannel) diff --git a/src/network/ssl/qsslsocket_schannel.cpp b/src/network/ssl/qsslsocket_schannel.cpp index f0e9e9c9d2..7ac032bd52 100644 --- a/src/network/ssl/qsslsocket_schannel.cpp +++ b/src/network/ssl/qsslsocket_schannel.cpp @@ -157,6 +157,75 @@ QT_BEGIN_NAMESPACE namespace { + +class SchannelBackend : public QTlsBackend +{ +private: + QString backendName() const override + { + return QTlsBackendFactory::builtinBackendNames[QTlsBackendFactory::nameIndexSchannel]; + } +}; + +class SchannelBackendBackendFactory : public QTlsBackendFactory +{ +private: + QString backendName() const override + { + return QTlsBackendFactory::builtinBackendNames[QTlsBackendFactory::nameIndexSchannel]; + } + QTlsBackend *create() const override + { + return new SchannelBackend; + } + + QList supportedProtocols() const override + { + QList protocols; + + protocols << QSsl::AnyProtocol; + protocols << QSsl::SecureProtocols; + protocols << QSsl::TlsV1_0; + protocols << QSsl::TlsV1_0OrLater; + protocols << QSsl::TlsV1_1; + protocols << QSsl::TlsV1_1OrLater; + protocols << QSsl::TlsV1_2; + protocols << QSsl::TlsV1_2OrLater; + + bool supportsTls13(); + if (supportsTls13()) { + protocols << QSsl::TlsV1_3; + protocols << QSsl::TlsV1_3OrLater; + } + + return protocols; + } + + QList supportedFeatures() const override + { + QList features; + + features << QSsl::SupportedFeature::ClientSideAlpn; + features << QSsl::SupportedFeature::ServerSideAlpn; + + return features; + } + + QList implementedClasses() const override + { + QList classes; + + classes << QSsl::ImplementedClass::Socket; + classes << QSsl::ImplementedClass::Certificate; + classes << QSsl::ImplementedClass::Key; + + return classes; + } +}; + +Q_GLOBAL_STATIC(SchannelBackendFactory, factory) + + SecBuffer createSecBuffer(void *ptr, unsigned long length, unsigned long bufferType) { return SecBuffer{ length, bufferType, ptr }; @@ -2144,93 +2213,12 @@ bool QSslSocketBackendPrivate::rootCertOnDemandLoadingAllowed() return allowRootCertOnDemandLoading && s_loadRootCertsOnDemand; } -QList QSslSocketPrivate::availableBackends() +void QSslSocketPrivate::registerAdHocFactory() { - return {QStringLiteral("schannel")}; -} - -QString QSslSocketPrivate::activeBackend() -{ - return availableBackends().first(); -} - -bool QSslSocketPrivate::loadBackend(const QString &backendName) -{ - if (backendName.size() && !availableBackends().contains(backendName)) { - qCWarning(lcSsl) << "A TLS backend with name" << backendName << "is not available"; - return false; - } - - static bool loaded = false; - static QBasicMutex mutex; - const QMutexLocker locker(&mutex); - if (loaded) { - qCWarning(lcSsl) << "You have already loaded the backend named:" << activeBackend(); - qCWarning(lcSsl) << "Cannot load:" << backendName; - return true; - } - // This code to be placed in qsslsocket.cpp and there - // the actual plugin to be loaded (so the result can be - // false if we, for example, failed to resolve OpenSSL - // symbols). - return loaded = true; -} - -QList QSslSocketPrivate::supportedProtocols(const QString &backendName) -{ - QList protocols; - if (backendName.size() && backendName != activeBackend()) { - qCWarning(lcSsl) << "Unexpected backend name" << backendName - << "no information about protocols supported can be found"; - return protocols; - } - - protocols << QSsl::AnyProtocol; - protocols << QSsl::SecureProtocols; - protocols << QSsl::TlsV1_0; - protocols << QSsl::TlsV1_0OrLater; - protocols << QSsl::TlsV1_1; - protocols << QSsl::TlsV1_1OrLater; - protocols << QSsl::TlsV1_2; - protocols << QSsl::TlsV1_2OrLater; - - if (supportsTls13()) { - protocols << QSsl::TlsV1_3; - protocols << QSsl::TlsV1_3OrLater; - } - - return protocols; -} - -QList QSslSocketPrivate::implementedClasses(const QString &backendName) -{ - QList classes; - if (backendName.size() && backendName != activeBackend()) { - qCWarning(lcSsl) << "Unexpected backend name" << backendName - << "no information about classes implemented can be found"; - return classes; - } - - classes << QSsl::ImplementedClass::Key; - classes << QSsl::ImplementedClass::Certificate; - classes << QSsl::ImplementedClass::Socket; - - return classes; -} - -QList QSslSocketPrivate::supportedFeatures(const QString &backendName) -{ - QList features; - if (backendName.size() && backendName != activeBackend()) { - qCWarning(lcSsl) << "Unexpected backend name" << backendName - << "no information about classes implemented can be found"; - return features; - } - - features << QSsl::SupportedFeature::ClientSideAlpn; - features << QSsl::SupportedFeature::ServerSideAlpn; - - return features; + // TLSTODO: this is a temporary solution, waiting for + // backends to move to ... plugins. + if (!factory()) + qCWarning(lcSsl, "Failed to create backend factory"); } QT_END_NAMESPACE diff --git a/src/network/ssl/qtlsbackend.cpp b/src/network/ssl/qtlsbackend.cpp new file mode 100644 index 0000000000..4183c1e2f1 --- /dev/null +++ b/src/network/ssl/qtlsbackend.cpp @@ -0,0 +1,299 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qtlsbackend_p.h" +#include "qsslsocket_p.h" +#include "qssl_p.h" + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QTlsBackendFactory_iid, QStringLiteral("/tlsbackends"))) + +const QString QTlsBackendFactory::builtinBackendNames[] = { + QStringLiteral("schannel"), + QStringLiteral("securetransport"), + QStringLiteral("openssl") +}; + + +QTlsBackend::QTlsBackend() = default; +QTlsBackend::~QTlsBackend() = default; + +const QString dummyName = QStringLiteral("dummyTLS"); + +QString QTlsBackend::backendName() const +{ + return dummyName; +} + +QSsl::TlsKey *QTlsBackend::createKey() const +{ + qCWarning(lcSsl, "Dummy TLS backend, cannot generate a key"); + return nullptr; +} + +QSsl::X509Certificate *QTlsBackend::createCertificate() const +{ + qCWarning(lcSsl, "Dummy TLS backend, cannot create a certificate"); + return nullptr; +} + +QSsl::TlsCryptograph *QTlsBackend::createTlsCryptograph() const +{ + qCWarning(lcSsl, "Dummy TLS backend, cannot create TLS session"); + return nullptr; +} + +QSsl::DtlsCryptograph *QTlsBackend::createDtlsCryptograph() const +{ + qCWarning(lcSsl, "Dummy TLS backend, cannot create DTLS session"); + return nullptr; +} + +QSsl::DtlsCookieVerifier *QTlsBackend::createDtlsCookieVerifier() const +{ + qCWarning(lcSsl, "Dummy TLS backend, cannot create DTLS cookie generator/verifier"); + return nullptr; +} + +QSsl::X509ChainVerifyPtr QTlsBackend::X509Verifier() const +{ + qCWarning(lcSsl, "Dummy TLS backend, cannot verify X509 chain"); + return nullptr; +} + +QSsl::X509PemReaderPtr QTlsBackend::X509PemReader() const +{ + qCWarning(lcSsl, "Dummy TLS backend, cannot read PEM format"); + return nullptr; +} + +QSsl::X509DerReaderPtr QTlsBackend::X509DerReader() const +{ + qCWarning(lcSsl, "Dummy TLS backend, don't know how to read DER"); + return nullptr; +} + +QSsl::X509Pkcs12ReaderPtr QTlsBackend::X509Pkcs12Reader() const +{ + qCWarning(lcSsl, "Dummy TLS backend, cannot read PKCS12"); + return nullptr; +} + +namespace { + +class BackEndFactoryCollection +{ +public: + void addFactory(QTlsBackendFactory *newFactory) + { + Q_ASSERT(newFactory); + Q_ASSERT(std::find(backendFactories.begin(), backendFactories.end(), newFactory) == backendFactories.end()); + const QMutexLocker locker(&collectionMutex); + backendFactories.push_back(newFactory); + } + + void removeFactory(QTlsBackendFactory *factory) + { + Q_ASSERT(factory); + const QMutexLocker locker(&collectionMutex); + const auto it = std::find(backendFactories.begin(), backendFactories.end(), factory); + Q_ASSERT(it != backendFactories.end()); + backendFactories.erase(it); + } + + bool tryPopulateCollection() + { + if (!loader()) + return false; + + static QBasicMutex mutex; + const QMutexLocker locker(&mutex); + if (loaded) + return true; + +#if QT_CONFIG(library) + loader->update(); +#endif + int index = 0; + while (loader->instance(index)) + ++index; + + // TLSTODO: obviously, this one should go away: + QSslSocketPrivate::registerAdHocFactory(); + + return loaded = true; + } + + QList backendNames() + { + QList names; + if (!tryPopulateCollection()) + return names; + + const QMutexLocker locker(&collectionMutex); + if (!backendFactories.size()) + return names; + + names.reserve(backendFactories.size()); + for (const auto *factory : backendFactories) + names.append(factory->backendName()); + + return names; + } + + QTlsBackendFactory *factory(const QString &name) + { + if (!tryPopulateCollection()) + return nullptr; + + const QMutexLocker locker(&collectionMutex); + const auto it = std::find_if(backendFactories.begin(), backendFactories.end(), + [&name](const auto *fct) {return fct->backendName() == name;}); + + return it == backendFactories.end() ? nullptr : *it; + } + +private: + std::vector backendFactories; + QMutex collectionMutex; + bool loaded = false; +}; + +Q_GLOBAL_STATIC(BackEndFactoryCollection, factories); + +} // unnamed namespace + +QTlsBackendFactory::QTlsBackendFactory() +{ + if (factories()) + factories->addFactory(this); +} + +QTlsBackendFactory::~QTlsBackendFactory() +{ + if (factories()) + factories->removeFactory(this); +} + +QString QTlsBackendFactory::backendName() const +{ + return dummyName; +} + +QList QTlsBackendFactory::availableBackendNames() +{ + if (!factories()) + return {}; + + return factories->backendNames(); +} + +QString QTlsBackendFactory::defaultBackendName() +{ + // We prefer native as default: + const auto names = availableBackendNames(); + auto name = builtinBackendNames[nameIndexSchannel]; + if (names.contains(name)) + return name; + name = builtinBackendNames[nameIndexSecureTransport]; + if (names.contains(name)) + return name; + name = builtinBackendNames[nameIndexOpenSSL]; + if (names.contains(name)) + return name; + + return {}; +} + +QTlsBackend *QTlsBackendFactory::create(const QString &backendName) +{ + if (!factories()) + return {}; + + if (const auto *fct = factories->factory(backendName)) + return fct->create(); + + qCWarning(lcSsl) << "Cannot create unknown backend named" << backendName; + return nullptr; +} + +QList QTlsBackendFactory::supportedProtocols(const QString &backendName) +{ + if (!factories()) + return {}; + + if (const auto *fct = factories->factory(backendName)) + return fct->supportedProtocols(); + + return {}; +} + +QList QTlsBackendFactory::supportedFeatures(const QString &backendName) +{ + if (!factories()) + return {}; + + if (const auto *fct = factories->factory(backendName)) + return fct->supportedFeatures(); + + return {}; +} + +QList QTlsBackendFactory::implementedClasses(const QString &backendName) +{ + if (!factories()) + return {}; + + if (const auto *fct = factories->factory(backendName)) + return fct->implementedClasses(); + + return {}; + +} + +QT_END_NAMESPACE diff --git a/src/network/ssl/qtlsbackend_p.h b/src/network/ssl/qtlsbackend_p.h new file mode 100644 index 0000000000..9c4f2d3eb8 --- /dev/null +++ b/src/network/ssl/qtlsbackend_p.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** 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 QTLSBACKEND_P_H +#define QTLSBACKEND_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 + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +QT_REQUIRE_CONFIG(ssl); + +QT_BEGIN_NAMESPACE + +class QByteArray; +class QIODevice; + +namespace QSsl { + +// Encapsulates key's data or backend-specific +// data-structure, like RSA/DSA/DH structs in OpenSSL. +class TlsKey; + +// Abstraction above OpenSSL's X509, or our generic +// 'derData'-based code. +class X509Certificate; + +// X509-related auxiliary functions, previously static +// member-functions in different classes. +using X509ChainVerifyPtr = QList (*)(const QList &chain, + const QString &hostName); +using X509PemReaderPtr = QList (*)(const QByteArray &pem, int count); +using X509DerReaderPtr = X509PemReaderPtr; +using X509Pkcs12ReaderPtr = bool (*)(QIODevice *device, QSslKey *key, QSslCertificate *cert, + QList *caCertificates, + const QByteArray &passPhrase); + +// TLS over TCP. Handshake, encryption/decryption. +class TlsCryptograph; + +// TLS over UDP. Handshake, encryption/decryption. +class DtlsCryptograph; + +// DTLS cookie: generation and verification. +class DtlsCookieVerifier; + +} // namespace QSsl + +// Factory, creating back-end specific implementations of +// different entities QSslSocket is using. +// TLSTODO: consider merging with ... it's own factory +// below, no real benefit in having this split. +class Q_NETWORK_EXPORT QTlsBackend : public QObject +{ + Q_OBJECT +public: + QTlsBackend(); + ~QTlsBackend() override; + + virtual QString backendName() const; + + // X509 and keys: + virtual QSsl::TlsKey *createKey() const; + virtual QSsl::X509Certificate *createCertificate() const; + + // TLS and DTLS: + virtual QSsl::TlsCryptograph *createTlsCryptograph() const; + virtual QSsl::DtlsCryptograph *createDtlsCryptograph() const; + virtual QSsl::DtlsCookieVerifier *createDtlsCookieVerifier() const; + + // X509 machinery: + virtual QSsl::X509ChainVerifyPtr X509Verifier() const; + virtual QSsl::X509PemReaderPtr X509PemReader() const; + virtual QSsl::X509DerReaderPtr X509DerReader() const; + virtual QSsl::X509Pkcs12ReaderPtr X509Pkcs12Reader() const; + + Q_DISABLE_COPY_MOVE(QTlsBackend) +}; + +// Factory for a backend. +class Q_NETWORK_EXPORT QTlsBackendFactory : public QObject +{ + Q_OBJECT +public: + QTlsBackendFactory(); + ~QTlsBackendFactory() override; + + virtual QString backendName() const = 0; + virtual QTlsBackend *create() const = 0; + virtual QList supportedProtocols() const = 0; + virtual QList supportedFeatures() const = 0; + virtual QList implementedClasses() const = 0; + + static QList availableBackendNames(); + static QString defaultBackendName(); + static QTlsBackend *create(const QString &backendName); + + static QList supportedProtocols(const QString &backendName); + static QList supportedFeatures(const QString &backendName); + static QList implementedClasses(const QString &backendName); + + // Built-in, this is what Qt provides out of the box (depending on OS): + static constexpr const int nameIndexSchannel = 0; + static constexpr const int nameIndexSecureTransport = 1; + static constexpr const int nameIndexOpenSSL = 2; + + static const QString builtinBackendNames[]; + + Q_DISABLE_COPY_MOVE(QTlsBackendFactory) +}; + +#define QTlsBackendFactory_iid "org.qt-project.Qt.QTlsBackendFactory" +Q_DECLARE_INTERFACE(QTlsBackendFactory, QTlsBackendFactory_iid); + + +QT_END_NAMESPACE + +#endif // QTLSBACKEND_P_H diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp index df35d93a63..bb77668b81 100644 --- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp +++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp @@ -56,6 +56,8 @@ #ifndef QT_NO_SSL +#include "private/qtlsbackend_p.h" + #ifndef QT_NO_OPENSSL #include "private/qsslsocket_openssl_p.h" #include "private/qsslsocket_openssl_symbols_p.h" @@ -163,6 +165,7 @@ public slots: #ifndef QT_NO_SSL private slots: + void backends(); void constructing(); void configNoOnDemandLoad(); void simpleConnect(); @@ -437,9 +440,13 @@ void tst_QSslSocket::initTestCase() #endif // QT_NO_SSL // Since a backend can be loaded only once by an application (this test in our case), - // we do backend testing here: + // we do backend testing here. + + // Before we tried to load anything, the active is the same thing as the default one: + QCOMPARE(QSslSocket::activeBackend(), QTlsBackendFactory::defaultBackendName()); + const QString nonExistingBackend = QStringLiteral("TheQtTLS"); - QCOMPARE(QSslSocket::loadBackend(nonExistingBackend), false); + QCOMPARE(QSslSocket::setActiveBackend(nonExistingBackend), false); QCOMPARE(QSslSocket::supportedProtocols(nonExistingBackend).size(), 0); QCOMPARE(QSslSocket::supportedFeatures(nonExistingBackend), QList()); QCOMPARE(QSslSocket::implementedClasses(nonExistingBackend), QList()); @@ -450,9 +457,9 @@ void tst_QSslSocket::initTestCase() const auto supportedFt = QSsl::SupportedFeature::ClientSideAlpn; QVERIFY(QSslSocket::availableBackends().contains(backendName)); - QCOMPARE(QSslSocket::loadBackend(backendName), true); + QCOMPARE(QSslSocket::setActiveBackend(backendName), true); QCOMPARE(QSslSocket::activeBackend(), backendName); - QCOMPARE(QSslSocket::loadBackend(backendName), true); // Already loaded, but not a fail. + QCOMPARE(QSslSocket::setActiveBackend(backendName), true); // We can do it again. QCOMPARE(QSslSocket::activeBackend(), backendName); const auto protocols = QSslSocket::supportedProtocols(); @@ -557,6 +564,131 @@ void tst_QSslSocket::proxyAuthenticationRequired(const QNetworkProxy &, QAuthent #ifndef QT_NO_SSL +struct MockTlsBackend : QTlsBackend +{ + MockTlsBackend(const QString &n) : name(n) {} + QString backendName() const override + { + return name; + } + QString name; +}; + +struct MockTlsFactory : QTlsBackendFactory +{ + MockTlsFactory(const QString &mockName) : name(mockName) + { + } + QString backendName() const override + { + return name; + } + + QList supportedFeatures() const override + { + return features; + } + QList supportedProtocols() const override + { + return protocols; + } + QList implementedClasses() const override + { + return classes; + } + QTlsBackend *create() const override + { + auto tls = new MockTlsBackend(name); + return tls; + } + QString name; + QList classes; + QList features; + QList protocols; +}; + +void tst_QSslSocket::backends() +{ + QFETCH_GLOBAL(const bool, setProxy); + if (setProxy) + QSKIP("Proxy is not interesting for backend test"); + + // We are here, protected by !QT_NO_SSL. Some backend must be pre-existing. + // Let's test the 'real' backend: + auto backendNames = QTlsBackendFactory::availableBackendNames(); + const auto sizeBefore = backendNames.size(); + QVERIFY(sizeBefore > 0); + + const auto builtinBackend = backendNames.first(); + const auto builtinProtocols = QSslSocket::supportedProtocols(builtinBackend); + QVERIFY(builtinProtocols.contains(QSsl::SecureProtocols)); + // Socket and ALPN are supported by all our backends: + const auto builtinClasses = QSslSocket::implementedClasses(builtinBackend); + QVERIFY(builtinClasses.contains(QSsl::ImplementedClass::Socket)); + const auto builtinFeatures = QSslSocket::supportedFeatures(builtinBackend); + QVERIFY(builtinFeatures.contains(QSsl::SupportedFeature::ClientSideAlpn)); + + { + // Verify that non-dummy backend can be created (and delete it): + const std::unique_ptr systemBackend(QTlsBackendFactory::create(backendNames.first())); + QVERIFY(systemBackend.get()); + } + + const auto protocols = QList{QSsl::SecureProtocols}; + const auto classes = QList{QSsl::ImplementedClass::Socket}; + const auto features = QList{QSsl::SupportedFeature::CertificateVerification}; + + const QString nameA = QStringLiteral("backend A"); + const QString nameB = QStringLiteral("backend B"); + const QString nonExisting = QStringLiteral("non-existing backend"); + + QVERIFY(!backendNames.contains(nameA)); + QVERIFY(!backendNames.contains(nameB)); + QVERIFY(!backendNames.contains(nonExisting)); + { + MockTlsFactory factoryA(nameA); + backendNames = QTlsBackendFactory::availableBackendNames(); + QVERIFY(backendNames.contains(nameA)); + QVERIFY(!backendNames.contains(nameB)); + QVERIFY(!backendNames.contains(nonExisting)); + + QCOMPARE(factoryA.supportedFeatures().size(), 0); + QCOMPARE(factoryA.supportedProtocols().size(), 0); + QCOMPARE(factoryA.implementedClasses().size(), 0); + + factoryA.protocols = protocols; + factoryA.classes = classes; + factoryA.features = features; + + // It's an overrider in some re-implemented factory: + QCOMPARE(factoryA.supportedProtocols(), protocols); + QCOMPARE(factoryA.supportedFeatures(), features); + QCOMPARE(factoryA.implementedClasses(), classes); + + // That's a helper function (static member function): + QCOMPARE(QTlsBackendFactory::supportedProtocols(nameA), protocols); + QCOMPARE(QTlsBackendFactory::supportedFeatures(nameA), features); + QCOMPARE(QTlsBackendFactory::implementedClasses(nameA), classes); + + MockTlsFactory factoryB(nameB); + QVERIFY(QTlsBackendFactory::availableBackendNames().contains(nameA)); + QVERIFY(QTlsBackendFactory::availableBackendNames().contains(nameB)); + QVERIFY(!QTlsBackendFactory::availableBackendNames().contains(nonExisting)); + + const std::unique_ptr backendA(QTlsBackendFactory::create(nameA)); + QVERIFY(backendA.get()); + QCOMPARE(backendA->backendName(), nameA); + + const std::unique_ptr nullBackend(QTlsBackendFactory::create(nonExisting)); + QCOMPARE(nullBackend.get(), nullptr); + } + backendNames = QTlsBackendFactory::availableBackendNames(); + QCOMPARE(backendNames.size(), sizeBefore); + // Check we cleaned up our factories: + QVERIFY(!backendNames.contains(nameA)); + QVERIFY(!backendNames.contains(nameB)); +} + void tst_QSslSocket::constructing() { const char readNotOpenMessage[] = "QIODevice::read (QSslSocket): device not open";