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>
This commit is contained in:
Timur Pocheptsov 2021-01-18 14:29:10 +01:00
parent 946facb1ae
commit 7cf8e5ada9
10 changed files with 903 additions and 324 deletions

View File

@ -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

View File

@ -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());
}
/*!

View File

@ -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 = {});

View File

@ -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

View File

@ -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()
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<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

View File

@ -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)

View File

@ -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()
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<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

View File

@ -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

View File

@ -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

View File

@ -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<QSsl::SupportedFeature>());
QCOMPARE(QSslSocket::implementedClasses(nonExistingBackend), QList<QSsl::ImplementedClass>());
@ -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<QSsl::SupportedFeature> supportedFeatures() const override
{
return features;
}
QList<QSsl::SslProtocol> supportedProtocols() const override
{
return protocols;
}
QList<QSsl::ImplementedClass> implementedClasses() const override
{
return classes;
}
QTlsBackend *create() const override
{
auto tls = new MockTlsBackend(name);
return tls;
}
QString name;
QList<QSsl::ImplementedClass> classes;
QList<QSsl::SupportedFeature> features;
QList<QSsl::SslProtocol> 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<QTlsBackend> systemBackend(QTlsBackendFactory::create(backendNames.first()));
QVERIFY(systemBackend.get());
}
const auto protocols = QList<QSsl::SslProtocol>{QSsl::SecureProtocols};
const auto classes = QList<QSsl::ImplementedClass>{QSsl::ImplementedClass::Socket};
const auto features = QList<QSsl::SupportedFeature>{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<QTlsBackend> backendA(QTlsBackendFactory::create(nameA));
QVERIFY(backendA.get());
QCOMPARE(backendA->backendName(), nameA);
const std::unique_ptr<QTlsBackend> 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";