diff options
author | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2021-01-18 14:29:10 +0100 |
---|---|---|
committer | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2021-01-22 09:09:46 +0100 |
commit | 7cf8e5ada9eac00b200141fdc80a2e76c0422411 (patch) | |
tree | 1e832df0dbdad5f7fb38aab6fbfa2a93b2ffa081 /src/network | |
parent | 946facb1ae5ed1cf54bb5141bc5ca20ddbb26755 (diff) |
QSsl: add a new private API
This is an abstraction for TLS backend and its factory, preparing to transition
to plugin-based design.
Task-number: QTBUG-65922
Change-Id: Ibe810e77fd1b715a6bea66cd3f44312b015ac274
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket.cpp | 88 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket.h | 2 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_mac.cpp | 150 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl.cpp | 192 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_p.h | 18 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_schannel.cpp | 160 | ||||
-rw-r--r-- | src/network/ssl/qtlsbackend.cpp | 299 | ||||
-rw-r--r-- | src/network/ssl/qtlsbackend_p.h | 177 |
9 files changed, 767 insertions, 320 deletions
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<QString> 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<QSsl::SslProtocol> 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<QSsl::ImplementedClass> 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<QSsl::SupportedFeature> 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<QString> availableBackends(); static QString activeBackend(); - static bool loadBackend(const QString &backendName); + static bool setActiveBackend(const QString &backendName); static QList<QSsl::SslProtocol> supportedProtocols(const QString &backendName = {}); static bool isProtocolSupported(QSsl::SslProtocol protocol, const QString &backendName = {}); static QList<QSsl::ImplementedClass> 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<QSsl::SslProtocol> supportedProtocols() const override + { + QList<QSsl::SslProtocol> 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<QSsl::SupportedFeature> supportedFeatures() const override + { + QList<QSsl::SupportedFeature> features; + features << QSsl::SupportedFeature::ClientSideAlpn; + + return features; + } + + QList<QSsl::ImplementedClass> implementedClasses() const override + { + QList<QSsl::ImplementedClass> 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<QString> 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<QSsl::SslProtocol> QSslSocketPrivate::supportedProtocols(const QString &backendName) -{ - QList<QSsl::SslProtocol> 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<QSsl::ImplementedClass> QSslSocketPrivate::implementedClasses(const QString &backendName) -{ - QList<QSsl::ImplementedClass> 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<QSsl::SupportedFeature> QSslSocketPrivate::supportedFeatures(const QString &backendName) -{ - QList<QSsl::SupportedFeature> 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<QSsl::SslProtocol> supportedProtocols() const override + { + QList<QSsl::SslProtocol> 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<QSsl::SupportedFeature> supportedFeatures() const override + { + QList<QSsl::SupportedFeature> 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<QSsl::ImplementedClass> implementedClasses() const override + { + QList<QSsl::ImplementedClass> 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<QString> QSslSocketPrivate::availableBackends() -{ - return {QStringLiteral("openssl")}; -} - -QString QSslSocketPrivate::activeBackend() +void QSslSocketPrivate::registerAdHocFactory() { - 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<QSsl::SslProtocol> QSslSocketPrivate::supportedProtocols(const QString &backendName) -{ - QList<QSsl::SslProtocol> 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<QSsl::ImplementedClass> QSslSocketPrivate::implementedClasses(const QString &backendName) -{ - QList<QSsl::ImplementedClass> 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<QSsl::SupportedFeature> QSslSocketPrivate::supportedFeatures(const QString &backendName) -{ - QList<QSsl::SupportedFeature> 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 <private/qsslcontext_openssl_p.h> #else @@ -67,6 +68,8 @@ class QSslContext; #include <QtCore/qlist.h> #include <QtCore/qstringlist.h> +#include <QtCore/qmutex.h> + #include <private/qringbuffer_p.h> #if defined(Q_OS_MAC) @@ -81,6 +84,8 @@ class QSslContext; #endif // !HCRYPTPROV_LEGACY #endif // Q_OS_WIN +#include <memory> + 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<QString> availableBackends(); - static QString activeBackend(); static bool loadBackend(const QString &backendName); - static QList<QSsl::SslProtocol> supportedProtocols(const QString &backendName); - static QList<QSsl::ImplementedClass> implementedClasses(const QString &backendName); - static QList<QSsl::SupportedFeature> 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<QTlsBackend> 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<QSsl::SslProtocol> supportedProtocols() const override + { + QList<QSsl::SslProtocol> 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<QSsl::SupportedFeature> supportedFeatures() const override + { + QList<QSsl::SupportedFeature> features; + + features << QSsl::SupportedFeature::ClientSideAlpn; + features << QSsl::SupportedFeature::ServerSideAlpn; + + return features; + } + + QList<QSsl::ImplementedClass> implementedClasses() const override + { + QList<QSsl::ImplementedClass> 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<QString> QSslSocketPrivate::availableBackends() -{ - return {QStringLiteral("schannel")}; -} - -QString QSslSocketPrivate::activeBackend() +void QSslSocketPrivate::registerAdHocFactory() { - 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<QSsl::SslProtocol> QSslSocketPrivate::supportedProtocols(const QString &backendName) -{ - QList<QSsl::SslProtocol> 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<QSsl::ImplementedClass> QSslSocketPrivate::implementedClasses(const QString &backendName) -{ - QList<QSsl::ImplementedClass> 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<QSsl::SupportedFeature> QSslSocketPrivate::supportedFeatures(const QString &backendName) -{ - QList<QSsl::SupportedFeature> 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 <QtCore/private/qfactoryloader_p.h> + +#include <QtCore/qmutex.h> + +#include <algorithm> +#include <vector> + +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<QString> backendNames() + { + QList<QString> 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<QTlsBackendFactory *> 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<QString> 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<QSsl::SslProtocol> QTlsBackendFactory::supportedProtocols(const QString &backendName) +{ + if (!factories()) + return {}; + + if (const auto *fct = factories->factory(backendName)) + return fct->supportedProtocols(); + + return {}; +} + +QList<QSsl::SupportedFeature> QTlsBackendFactory::supportedFeatures(const QString &backendName) +{ + if (!factories()) + return {}; + + if (const auto *fct = factories->factory(backendName)) + return fct->supportedFeatures(); + + return {}; +} + +QList<QSsl::ImplementedClass> 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 <private/qtnetworkglobal_p.h> + +#include <QtNetwork/qsslcertificate.h> +#include <QtNetwork/qsslerror.h> +#include <QtNetwork/qsslkey.h> +#include <QtNetwork/qssl.h> + +#include <QtCore/qobject.h> +#include <QtCore/qglobal.h> +#include <QtCore/qstring.h> +#include <QtCore/qlist.h> +#include <QtCore/qmap.h> + +#include <vector> +#include <memory> + +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<QSslError> (*)(const QList<QSslCertificate> &chain, + const QString &hostName); +using X509PemReaderPtr = QList<QSslCertificate> (*)(const QByteArray &pem, int count); +using X509DerReaderPtr = X509PemReaderPtr; +using X509Pkcs12ReaderPtr = bool (*)(QIODevice *device, QSslKey *key, QSslCertificate *cert, + QList<QSslCertificate> *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<QSsl::SslProtocol> supportedProtocols() const = 0; + virtual QList<QSsl::SupportedFeature> supportedFeatures() const = 0; + virtual QList<QSsl::ImplementedClass> implementedClasses() const = 0; + + static QList<QString> availableBackendNames(); + static QString defaultBackendName(); + static QTlsBackend *create(const QString &backendName); + + static QList<QSsl::SslProtocol> supportedProtocols(const QString &backendName); + static QList<QSsl::SupportedFeature> supportedFeatures(const QString &backendName); + static QList<QSsl::ImplementedClass> 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 |