QSslSocket - introduce the API providing information about backends

This API gives the names of available backends and provides a basic
information about features/protocols supported by those backends.
Also, it has the 'loadBackend' functions which allow to select
a particular backend (which are becoming plugins).

At the moment, the implementation is still 'hardcoded', the
follow-up patch will allow to select different backends in runtime.

Task-number: QTBUG-65922
Change-Id: I05877de9c02857594e76b24d52e7578bdb01df69
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
bb10
Timur Pocheptsov 2021-01-14 13:54:18 +01:00
parent 509196b1d9
commit ee89f3a6e3
9 changed files with 561 additions and 8 deletions

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
@ -243,4 +243,51 @@ Q_LOGGING_CATEGORY(lcSsl, "qt.network.ssl");
\value UnknownAlertMessage
*/
/*!
\enum QSsl::ImplementedClass
\brief Enumerates classes that a TLS backend implements
\relates QSslSocket
\since 6.1
\ingroup network
\ingroup ssl
\inmodule QtNetwork
In QtNetwork, some classes have backend-specific implementation and thus
can be left unimplemented. Enumerators in this enum indicate, which class
has a working implementation in the backend.
\value Key Class QSslKey.
\value Certificate Class QSslCertificate.
\value Socket Class QSslSocket.
\value DiffieHellman Class QSslDiffieHellmanParameters
\value EllipticCurve Class QSslEllipticCurve
\value Dtls Classes QDtls and QDtlsClientVerifier
*/
/*!
\enum QSsl::SupportedFeature
\brief Enumerates possible features that a TLS backend supports
\relates QSslSocket
\since 6.1
\ingroup network
\ingroup ssl
\inmodule QtNetwork
In QtNetwork TLS-related classes have public API, that may be left unimplemented
by some backend, for example, our SecureTransport backend does not support
server-side ALPN. Enumerators from SupportedFeature enum indicate that a particular
feature is supported.
\value CertificateVerification Indicates that QSslCertificate::verify() is
implemented by the backend.
\value ClientSideAlpn Client-side ALPN (Application Layer Protocol Negotiation).
\value ServerSideAlpn Server-side ALPN.
\value Ocsp OCSP stapling (Online Certificate Status Protocol).
\value Psk Pre-shared keys.
\value SessionTicket Session tickets.
\value Alerts Information about alert messages sent and received.
*/
QT_END_NAMESPACE

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
@ -149,6 +149,26 @@ namespace QSsl {
UnknownAlertMessage = 255
};
enum class ImplementedClass
{
Key = 0x1,
Certificate = 0x2,
Socket = 0x4,
DiffieHellman = 0x8,
EllipticCurve = 0x10,
Dtls = 0x20
};
enum class SupportedFeature
{
CertificateVerification = 0x1,
ClientSideAlpn = 0x2,
ServerSideAlpn = 0x4,
Ocsp = 0x8,
Psk = 0x10,
SessionTicket = 0x20,
Alerts = 040
};
}
Q_DECLARE_OPERATORS_FOR_FLAGS(QSsl::SslOptions)

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2021 The Qt Company Ltd.
** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
** Contact: https://www.qt.io/licensing/
**
@ -1546,6 +1546,150 @@ QString QSslSocket::sslLibraryBuildVersionString()
return QSslSocketPrivate::sslLibraryBuildVersionString();
}
/*!
\since 6.1
Returns the names of the currently available backends. These names
are in lower case, e.g. "openssl", "securetransport", "schannel"
(similar to the already existing feature names for TLS backends in Qt).
\sa activeBackend()
*/
QList<QString> QSslSocket::availableBackends()
{
return QSslSocketPrivate::availableBackends();
}
/*!
\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.
\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.
\sa loadBackend(), availableBackends()
*/
QString QSslSocket::activeBackend()
{
return QSslSocketPrivate::activeBackend();
}
/*!
\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().
\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).
\sa activeBackend(), availableBackends()
*/
bool QSslSocket::loadBackend(const QString &backendName)
{
return QSslSocketPrivate::loadBackend(backendName);
}
/*!
\since 6.1
If a backend with name \a backendName is available, this function returns the
list of TLS protocol versions supported by this backend. An empty \a backendName
is understood as a query about the currently active backend. Otherwise, this
function returns an empty list.
\sa availableBackends(), activeBackend(), isProtocolSupported()
*/
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);
}
/*!
\since 6.1
Returns true if \a protocol is supported by a backend named \a backendName. An empty
\a backendName is understood as a query about the currently active backend.
\sa supportedProtocols()
*/
bool QSslSocket::isProtocolSupported(QSsl::SslProtocol protocol, const QString &backendName)
{
const auto versions = supportedProtocols(backendName);
return versions.contains(protocol);
}
/*!
\since 6.1
This function returns backend-specific classes implemented by the backend named
\a backendName. An empty \a backendName is understood as a query about the
currently active backend.
\sa QSsl::ImplementedClass, activeBackend(), isClassImplemented()
*/
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);
}
/*!
\since 6.1
Returns true if a class \cl is implemented by the backend named \a backendName. An empty
\a backendName is understood as a query about the currently active backend.
\sa implementedClasses()
*/
bool QSslSocket::isClassImplemented(QSsl::ImplementedClass cl, const QString &backendName)
{
return implementedClasses(backendName).contains(cl);
}
/*!
\since 6.1
This function returns features supported by a backend named \a backendName.
An empty \a backendName is understood as a query about the currently active backend.
\sa QSsl::SupportedFeature, activeBackend()
*/
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);
}
/*!
\since 6.1
Returns true if a feature \a ft is supported by a backend named \a backendName. An empty
\a backendName is understood as a query about the currently active backend.
\sa QSsl::SupportedFeature, supportedFeatures()
*/
bool QSslSocket::isFeatureSupported(QSsl::SupportedFeature ft, const QString &backendName)
{
return supportedFeatures(backendName).contains(ft);
}
/*!
Starts a delayed SSL handshake for a client connection. This
function can be called when the socket is in the \l ConnectedState

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
@ -163,6 +163,16 @@ public:
static long sslLibraryBuildVersionNumber();
static QString sslLibraryBuildVersionString();
static QList<QString> availableBackends();
static QString activeBackend();
static bool loadBackend(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 = {});
static bool isClassImplemented(QSsl::ImplementedClass cl, const QString &backendName = {});
static QList<QSsl::SupportedFeature> supportedFeatures(const QString &backendName = {});
static bool isFeatureSupported(QSsl::SupportedFeature feat, const QString &backendName = {});
void ignoreSslErrors(const QList<QSslError> &errors);
void continueInterruptedHandshake();

View File

@ -1,5 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org>
** Contact: https://www.qt.io/licensing/
**
@ -1551,4 +1552,90 @@ bool QSslSocketBackendPrivate::startHandshake()
}
}
QList<QString> QSslSocketPrivate::availableBackends()
{
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;
}
QT_END_NAMESPACE

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Copyright (C) 2021 The Qt Company Ltd.
** Copyright (C) 2014 Governikus GmbH & Co. KG
** Contact: https://www.qt.io/licensing/
**
@ -2510,5 +2510,111 @@ bool QSslSocketBackendPrivate::importPkcs12(QIODevice *device,
return true;
}
QList<QString> QSslSocketPrivate::availableBackends()
{
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;
}
QT_END_NAMESPACE

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
@ -204,6 +204,12 @@ 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);
private:
static bool ensureLibraryLoaded();
static void ensureCiphersAndCertsLoaded();

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
@ -2144,4 +2144,93 @@ bool QSslSocketBackendPrivate::rootCertOnDemandLoadingAllowed()
return allowRootCertOnDemandLoading && s_loadRootCertsOnDemand;
}
QList<QString> QSslSocketPrivate::availableBackends()
{
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;
}
QT_END_NAMESPACE

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2021 The Qt Company Ltd.
** Copyright (C) 2014 Governikus GmbH & Co. KG.
** Contact: https://www.qt.io/licensing/
**
@ -435,6 +435,50 @@ void tst_QSslSocket::initTestCase()
QSKIP("No network test server available");
#endif // QT_TEST_SERVER
#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:
const QString nonExistingBackend = QStringLiteral("TheQtTLS");
QCOMPARE(QSslSocket::loadBackend(nonExistingBackend), false);
QCOMPARE(QSslSocket::supportedProtocols(nonExistingBackend).size(), 0);
QCOMPARE(QSslSocket::supportedFeatures(nonExistingBackend), QList<QSsl::SupportedFeature>());
QCOMPARE(QSslSocket::implementedClasses(nonExistingBackend), QList<QSsl::ImplementedClass>());
const QString backendName = QSslSocket::activeBackend();
// Implemented by all our existing backends:
const auto implemented = QSsl::ImplementedClass::Socket;
const auto supportedFt = QSsl::SupportedFeature::ClientSideAlpn;
QVERIFY(QSslSocket::availableBackends().contains(backendName));
QCOMPARE(QSslSocket::loadBackend(backendName), true);
QCOMPARE(QSslSocket::activeBackend(), backendName);
QCOMPARE(QSslSocket::loadBackend(backendName), true); // Already loaded, but not a fail.
QCOMPARE(QSslSocket::activeBackend(), backendName);
const auto protocols = QSslSocket::supportedProtocols();
QVERIFY(protocols.size() > 0);
// 'Any' and 'Secure', since they are always present:
QVERIFY(protocols.contains(QSsl::AnyProtocol));
QVERIFY(protocols.contains(QSsl::SecureProtocols));
const auto protocolsForNamed = QSslSocket::supportedProtocols(backendName);
QCOMPARE(protocols, protocolsForNamed);
// Any and secure, new versions are coming, old
// go away, nothing more specific.
QVERIFY(protocolsForNamed.contains(QSsl::AnyProtocol));
QVERIFY(protocolsForNamed.contains(QSsl::SecureProtocols));
QCOMPARE(QSslSocket::isProtocolSupported(QSsl::SecureProtocols), true);
QCOMPARE(QSslSocket::isProtocolSupported(QSsl::SecureProtocols, backendName), true);
const auto classes = QSslSocket::implementedClasses();
QVERIFY(classes.contains(implemented));
QVERIFY(QSslSocket::isClassImplemented(implemented));
QVERIFY(QSslSocket::isClassImplemented(implemented, backendName));
const auto features = QSslSocket::supportedFeatures();
QVERIFY(features.contains(QSsl::SupportedFeature(supportedFt)));
QVERIFY(QSslSocket::isFeatureSupported(QSsl::SupportedFeature(supportedFt)));
QVERIFY(QSslSocket::isFeatureSupported(QSsl::SupportedFeature(supportedFt), backendName));
}
void tst_QSslSocket::init()