Unify QSslServer from QtWebSockets and QtHttpServer into QtNetwork

Both QtWeSockets and QtHttpServer has a QSslServer class that is useful
elsewhere. They are different though, so the new class has features
from both versions.

[ChangeLog][QtNetwork] Unify QSslServer from QtWebSockets and QtHttpServer into QtNetwork

Task-number: QTBUG-100823
Change-Id: I523f04db39297ceb9b258f673eb12deecfc6886c
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Øystein Heskestad 2022-05-03 15:58:44 +02:00 committed by Mårten Nordheim
parent 782fbe0f63
commit d631e581c0
11 changed files with 934 additions and 0 deletions

View File

@ -330,6 +330,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/qsslserver.cpp ssl/qsslserver.h ssl/qsslserver_p.h
)
qt_internal_extend_target(Network CONDITION QT_FEATURE_dtls AND QT_FEATURE_ssl

View File

@ -0,0 +1,287 @@
// Copyright (C) 2022 The Qt Company Ltd.
// Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
/*!
\class QSslServer
\ingroup network
\ingroup ssl
\inmodule QtNetwork
\since 6.4
\brief Implements an encrypted, secure TCP server over TLS.
Class to use in place of QTcpServer to implement TCP server using
Transport Layer Security (TLS).
To configure the secure handshake settings, use the applicable setter
functions on a QSslConfiguration object, and then use it as a argument
to the setSslConfiguration() function. All following incoming
connections handled will use these settings.
To start listening to incoming connections use the listen() function
inherited from QTcpServer. Other settings can be configured by using the
setter functions inherited from the QTcpServer class.
Connect to the signals of this class to respond to the incoming connection
attempts. They are the same as the signals on QSslSocket, but also
passes a pointer to the socket in question.
When responding to the pendingConnectionAvailable() signal, use the
nextPendingConnection() function to fetch the next incoming connection and
take it out of the pending connection queue. The QSslSocket is a child of
the QSslServer and will be deleted when the QSslServer is deleted. It is
still a good a good idea to destroy the object explicitly when you are done
with it, to avoid wasting memory.
\sa QTcpServer, QSslConfiguration, QSslSocket
*/
/*!
\fn void QSslServer::peerVerifyError(QSslSocket *socket, const QSslError &error)
QSslServer can emit this signal several times during the SSL handshake,
before encryption has been established, to indicate that an error has
occurred while establishing the identity of the peer. The \a error is
usually an indication that \a socket is unable to securely identify the
peer.
This signal provides you with an early indication when something's wrong.
By connecting to this signal, you can manually choose to tear down the
connection from inside the connected slot before the handshake has
completed. If no action is taken, QSslServer will proceed to emitting
sslErrors().
\sa sslErrors()
*/
/*!
\fn void QSslServer::sslErrors(QSslSocket *socket, const QList<QSslError> &errors);
QSslServer emits this signal after the SSL handshake to indicate that one
or more errors have occurred while establishing the identity of the
peer. The errors are usually an indication that \a socket is unable to
securely identify the peer. Unless any action is taken, the connection
will be dropped after this signal has been emitted.
If you want to continue connecting despite the errors that have occurred,
you must call QSslSocket::ignoreSslErrors() from inside a slot connected to
this signal. If you need to access the error list at a later point, you
can call sslHandshakeErrors().
\a errors contains one or more errors that prevent QSslSocket from
verifying the identity of the peer.
\note You cannot use Qt::QueuedConnection when connecting to this signal,
or calling QSslSocket::ignoreSslErrors() will have no effect.
\sa peerVerifyError()
*/
/*!
\fn void QSslServer::errorOccurred(QSslSocket *socket, QAbstractSocket::SocketError socketError)
This signal is emitted after an error occurred during handshake. The
\a socketError parameter describes the type of error that occurred.
The \a socket is automatically deleted after this signal is emitted if the
socket handshake has not reached encrypted state. But if the \a socket is
successfully encrypted, it is inserted into the QSslServer's pending
connections queue. When the user has called
QTcpServer::nextPendingConnection() it is the user's responsibility to
destroy the \a socket or the \a socket will not be destroyed until the
QSslServer object is destroyed. If an error occurs on a \a socket after
it has been inserted into the pending connections queue, this signal
will not be emitted, and the \a socket will not be removed or destroyed.
\note You cannot use Qt::QueuedConnection when connecting to this signal,
or the \a socket will have been already destroyed when the signal is
handled.
\sa QSslSocket::error(), errorString()
*/
/*!
\fn void QSslServer::preSharedKeyAuthenticationRequired(QSslSocket *socket,
QSslPreSharedKeyAuthenticator *authenticator)
QSslServer emits this signal when \a socket negotiates a PSK ciphersuite,
and therefore PSK authentication is then required.
When using PSK, the server must supply a valid identity and a valid pre
shared key, in order for the SSL handshake to continue.
Applications can provide this information in a slot connected to this
signal, by filling in the passed \a authenticator object according to their
needs.
\note Ignoring this signal, or failing to provide the required credentials,
will cause the handshake to fail, and therefore the connection to be aborted.
\note The \a authenticator object is owned by the \a socket and must not be
deleted by the application.
\sa QSslPreSharedKeyAuthenticator
*/
/*!
\fn void QSslServer::alertSent(QSslSocket *socket, QSsl::AlertLevel level, QSsl::AlertType type,
const QString &description)
QSslServer emits this signal if an alert message was sent from \a socket
to a peer. \a level describes if it was a warning or a fatal error.
\a type gives the code of the alert message. When a textual description
of the alert message is available, it is supplied in \a description.
\note This signal is mostly informational and can be used for debugging
purposes, normally it does not require any actions from the application.
\note Not all backends support this functionality.
\sa alertReceived(), QSsl::AlertLevel, QSsl::AlertType
*/
/*!
\fn void QSslServer::alertReceived(QSslSocket *socket, QSsl::AlertLevel level, QSsl::AlertType
type, const QString &description)
QSslServer emits this signal if an alert message was received by the
\a socket from a peer. \a level tells if the alert was fatal or it was a
warning. \a type is the code explaining why the alert was sent.
When a textual description of the alert message is available, it is
supplied in \a description.
\note The signal is mostly for informational and debugging purposes and does not
require any handling in the application. If the alert was fatal, underlying
backend will handle it and close the connection.
\note Not all backends support this functionality.
\sa alertSent(), QSsl::AlertLevel, QSsl::AlertType
*/
/*!
\fn void QSslServer::handshakeInterruptedOnError(QSslSocket *socket, const QSslError &error)
QSslServer emits this signal if a certificate verification error was found
by \a socket and if early error reporting was enabled in QSslConfiguration.
An application is expected to inspect the \a error and decide if it wants
to continue the handshake, or abort it and send an alert message to the
peer. The signal-slot connection must be direct.
\sa QSslSocket::continueInterruptedHandshake(), sslErrors(),
QSslConfiguration::setHandshakeMustInterruptOnError()
*/
#include "qsslserver.h"
#include "qsslserver_p.h"
#include <QtNetwork/QSslSocket>
#include <QtNetwork/QSslCipher>
QT_BEGIN_NAMESPACE
/*!
\internal
*/
QSslServerPrivate::QSslServerPrivate() :
sslConfiguration(QSslConfiguration::defaultConfiguration())
{
}
/*!
Constructs a new QSslServer with the given \a parent.
*/
QSslServer::QSslServer(QObject *parent) :
QTcpServer(QAbstractSocket::TcpSocket, *new QSslServerPrivate, parent)
{
}
/*!
Destroys the QSslServer.
All open connections are closed.
*/
QSslServer::~QSslServer()
{
}
/*!
Sets the \a sslConfiguration to use for all following incoming connections.
This must be called before listen() to ensure that the desired
configuration was in use during all handshakes.
\sa QSslSocket::setSslConfiguration()
*/
void QSslServer::setSslConfiguration(const QSslConfiguration &sslConfiguration)
{
Q_D(QSslServer);
d->sslConfiguration = sslConfiguration;
}
/*!
Returns the current ssl configuration.
*/
QSslConfiguration QSslServer::sslConfiguration() const
{
const Q_D(QSslServer);
return d->sslConfiguration;
}
/*!
Called when a new connection is established.
Converts \a socket to a QSslSocket.
\reimp
*/
void QSslServer::incomingConnection(qintptr socket)
{
QSslSocket *pSslSocket = new QSslSocket(this);
pSslSocket->setSslConfiguration(sslConfiguration());
if (Q_LIKELY(pSslSocket->setSocketDescriptor(socket))) {
connect(pSslSocket, &QSslSocket::peerVerifyError,
[this, pSslSocket](const QSslError &error) {
Q_EMIT peerVerifyError(pSslSocket, error);
});
connect(pSslSocket, &QSslSocket::sslErrors,
[this, pSslSocket](const QList<QSslError> &errors) {
Q_EMIT sslErrors(pSslSocket, errors);
});
connect(pSslSocket, &QAbstractSocket::errorOccurred,
[this, pSslSocket](QAbstractSocket::SocketError error) {
Q_EMIT errorOccurred(pSslSocket, error);
if (!pSslSocket->isEncrypted())
pSslSocket->deleteLater();
});
connect(pSslSocket, &QSslSocket::encrypted, [this, pSslSocket]() {
pSslSocket->disconnect();
addPendingConnection(pSslSocket);
});
connect(pSslSocket, &QSslSocket::preSharedKeyAuthenticationRequired,
[this, pSslSocket](QSslPreSharedKeyAuthenticator *authenticator) {
Q_EMIT preSharedKeyAuthenticationRequired(pSslSocket, authenticator);
});
connect(pSslSocket, &QSslSocket::alertSent,
[this, pSslSocket](QSsl::AlertLevel level, QSsl::AlertType type,
const QString &description) {
Q_EMIT alertSent(pSslSocket, level, type, description);
});
connect(pSslSocket, &QSslSocket::alertReceived,
[this, pSslSocket](QSsl::AlertLevel level, QSsl::AlertType type,
const QString &description) {
Q_EMIT alertReceived(pSslSocket, level, type, description);
});
connect(pSslSocket, &QSslSocket::handshakeInterruptedOnError,
[this, pSslSocket](const QSslError &error) {
Q_EMIT handshakeInterruptedOnError(pSslSocket, error);
});
Q_EMIT startedEncryptionHandshake(pSslSocket);
pSslSocket->startServerEncryption();
}
}
QT_END_NAMESPACE

View File

@ -0,0 +1,58 @@
// Copyright (C) 2022 The Qt Company Ltd.
// Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QSSLSERVER_H
#define QSSLSERVER_H
#include <QtNetwork/QTcpServer>
QT_REQUIRE_CONFIG(ssl);
#include <QtNetwork/QSslError>
#include <QtNetwork/QSslConfiguration>
#include <QtNetwork/QSslPreSharedKeyAuthenticator>
#include <QtNetwork/QSslSocket>
#include <QtCore/QList>
QT_BEGIN_NAMESPACE
class QSslSocket;
class QSslServerPrivate;
class Q_NETWORK_EXPORT QSslServer : public QTcpServer
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(QSslServer)
public:
explicit QSslServer(QObject *parent = nullptr);
~QSslServer() override;
void setSslConfiguration(const QSslConfiguration &sslConfiguration);
QSslConfiguration sslConfiguration() const;
Q_SIGNALS:
void sslErrors(QSslSocket *socket, const QList<QSslError> &errors);
void peerVerifyError(QSslSocket *socket, const QSslError &error);
void errorOccurred(QSslSocket *socket, QAbstractSocket::SocketError error);
void preSharedKeyAuthenticationRequired(QSslSocket *socket,
QSslPreSharedKeyAuthenticator *authenticator);
void alertSent(QSslSocket *socket, QSsl::AlertLevel level,
QSsl::AlertType type, const QString &description);
void alertReceived(QSslSocket *socket, QSsl::AlertLevel level,
QSsl::AlertType type, const QString &description);
void handshakeInterruptedOnError(QSslSocket *socket, const QSslError &error);
void startedEncryptionHandshake(QSslSocket *socket);
protected:
void incomingConnection(qintptr socket) override;
private:
Q_DECLARE_PRIVATE(QSslServer)
};
QT_END_NAMESPACE
#endif // QSSLSERVER_H

View File

@ -0,0 +1,37 @@
// Copyright (C) 2022 The Qt Company Ltd.
// Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QSSLSERVER_P_H
#define QSSLSERVER_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 <QtNetwork/QSslConfiguration>
#include <QtNetwork/private/qtcpserver_p.h>
QT_BEGIN_NAMESPACE
class Q_NETWORK_EXPORT QSslServerPrivate : public QTcpServerPrivate
{
public:
Q_DECLARE_PUBLIC(QSslServer)
QSslServerPrivate();
QSslConfiguration sslConfiguration;
};
QT_END_NAMESPACE
#endif // QSSLSERVER_P_H

View File

@ -14,6 +14,7 @@ if(QT_FEATURE_private_tests AND QT_FEATURE_ssl)
add_subdirectory(qsslsocket_onDemandCertificates_static)
# add_subdirectory(qasn1element)
add_subdirectory(qssldiffiehellmanparameters)
add_subdirectory(qsslserver)
endif()
if(QT_FEATURE_dtls AND QT_FEATURE_private_tests AND QT_FEATURE_ssl)
add_subdirectory(qdtlscookie)

View File

@ -0,0 +1,19 @@
if(NOT QT_FEATURE_private_tests)
return()
endif()
#####################################################################
## tst_qsslserver Test:
#####################################################################
# Collect test data
list(APPEND test_data "certs")
qt_internal_add_test(tst_qsslserver
SOURCES
tst_qsslserver.cpp
PUBLIC_LIBRARIES
Qt::CorePrivate
Qt::NetworkPrivate
TESTDATA ${test_data}
)

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC6TCCAdECCC/r9KvmbWTKMA0GCSqGSIb3DQEBCwUAMDUxFDASBgNVBAMMC0F1
c3dlaXNBcHAyMR0wGwYDVQQFExQxODIzNTE0MTY0NzI5NDg5NDM3MTAiGA8xOTcw
MDEwMTAwMDAwMFoYDzk5OTkxMjMxMjM1OTU5WjA1MRQwEgYDVQQDDAtBdXN3ZWlz
QXBwMjEdMBsGA1UEBRMUMTgyMzUxNDE2NDcyOTQ4OTQzNzEwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCahBpcZyr+PJBCpolzQeFVvDKABwlpdRKGZ8qq
jD4sq2L7VlBJslgJGv5vsB5oJbnX1FFEu4Uw2kYb/LhnFCEXEFtGKRpWOEZOOqWb
4l4q2MCa82ZCoIDt8yoAt0sSShbtR6pjW+l0lwAOEpfGvMaMVo5JUyspRxhl1dSu
sS2Wf65zliqF5VSM2r4xMfJ6LVytxDZsGfTe/HFT2OYYrF+UQZg0mNL39rYWOK4R
xoOz8eLl3K5hKuHNfn5zPt5QtMhaIvebijBg23xJpl+BeoS37WzaK1f+NyWZKPFb
rttvSnFxpkyRHqJJ5piNGH6pkQ1+zhd7uh7eOIwxktjYBOFzAgMBAAEwDQYJKoZI
hvcNAQELBQADggEBADw3MYPft+X78OK/2HAltzsKjfxv/D5qVizm9hcyG1GYe5pS
qgFn0trCyJopYdbRr+hP7CuHwMmv62CZiHSog3CBPoUh19JENUDGbHXxTEFleB0i
Fd8I2+WvRjbQ+ehaeTJPx88v5kkJnB2tZUNZuhEws8emCwr1G0TQv1tRYCR1Lp9i
8/I3FSFpL1zyk47WfM/THa279MPw9WtrFGA6oi36gH9mYxek7n/zQTVi54xDx9GT
KigBYqavjFdNXryjLTCCtJpMTDePgP66NAUnxn0D/amI2vSbIN++PSTsBm+n4Ti5
QW/ShFQDNb4bDiwjtTKCeKwvAp2/6GSHVkYy28M=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAmoQaXGcq/jyQQqaJc0HhVbwygAcJaXUShmfKqow+LKti+1ZQ
SbJYCRr+b7AeaCW519RRRLuFMNpGG/y4ZxQhFxBbRikaVjhGTjqlm+JeKtjAmvNm
QqCA7fMqALdLEkoW7UeqY1vpdJcADhKXxrzGjFaOSVMrKUcYZdXUrrEtln+uc5Yq
heVUjNq+MTHyei1crcQ2bBn03vxxU9jmGKxflEGYNJjS9/a2FjiuEcaDs/Hi5dyu
YSrhzX5+cz7eULTIWiL3m4owYNt8SaZfgXqEt+1s2itX/jclmSjxW67bb0pxcaZM
kR6iSeaYjRh+qZENfs4Xe7oe3jiMMZLY2AThcwIDAQABAoIBAFjgvc0C5t8AdTZx
VsS+U2Aedang4lAPsE0xbIj3TFgjaTcLKfmKJUtvhIU39/WOJbz4+pofhvhXxVYZ
4vQfxvzeQrIzuFt52S7sWxA0gFgC/57hfKO8cQzt/u4UgJEPnupze5XVa47NwJFX
rof5U/erXgLdXQlMRMNm4QRvE7rp58E2MkSYNur0Xgy9L7cRcUQJ8iuMaxBpOzhS
fbNFi5zT7RCGcQSIDcb1JFlgs5tMUs6jzLoDSVD2+vvsN4i4LAAPkJSGTGed5vY1
xn4G8KPR4HHrnBYEb0SGu4ZTznOnQ+JSKhQrbnvEzXM4RTfjqn0YvF8x70+pWSMi
Fb4mlBECgYEAzW82O79HAlMm8LD7J4byPfVc/1M5/JOnE9H+RR5Vt4jZQGyjCmJu
cj4UeZyVim0xg30sSYrJ2Urd27CtHp+sMgHkvJt3/ZgcfMZJbMKNGq/OUtV8s/cA
nkU++/LgeW8r7wpaDjT7bfnOdcf16mYoXrmk0rTJvRqGXCBvCxtt5bsCgYEAwIxu
vZjPV4Vu/VX6sH2d31D9EFZuZKjGhqukFVtRqLbeosqT9mA+LhQ/wP5qoR2gLQbe
EwxJLJwGFjUhyhbHNlo6oHv3fWkzmHIMPwDRRI3Ktwi/50SwNSnyERUQcLaiwqKx
BqaxPYNnspUt0nKE0LFZsSlrfEyxajqAlUEgm6kCgYAV+uQumFScpxDvh8AXhpS8
lFgS6XC22YVy1XEDLC+3p2i3P+hh4A45IvNF378QRIabrvTiGXtnSF9cdhbPw/3E
i/dRRsEb3P6PSxfoDxjR1iWZL0Zcav0h8f6/LkleNMralJz2EC0moye36mEhZzTC
jdJYyQccuI3PpZi7839aqQKBgGezOnEiO4kHdB88jyc+gCglliWWZx4PR9x/1H8s
D26uDnneYJHwg4yNm0h1vTfInNujNzdLBp3f6edL9kbAvcmoDqsgGMqSPhd8VNwZ
tJsXQnYRYElN1RjM3nIUxiXuNvpcZLsQS6S1gMPNVEBjLOS4n3WquRjYtTRhDZ9U
1BsBAoGAUFrIatOLFhcgaqENHyUbMx5uSx0lIfF6Xd5KIAgi+btdmugHe+NK8Cd2
Rc2bQLQ9K1SvKFX6nFuEsGxnXkKuyhL/j0Kgm8nZin4uAcrtFnNdFumvCL6YgYSc
IvvM+uVfGEdbqm4pTuiLBfzOXIIy3kVlLGo402QG1pBzOtmsRMs=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC5TCCAc0CCAO22gNi0v20MA0GCSqGSIb3DQEBCwUAMDMxFDASBgNVBAMMC0F1
c3dlaXNBcHAyMRswGQYDVQQFExIyNTIxMTE1NjY3NjM2MjExODgwIhgPMTk3MDAx
MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowMzEUMBIGA1UEAwwLQXVzd2Vpc0Fw
cDIxGzAZBgNVBAUTEjI1MjExMTU2Njc2MzYyMTE4ODCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAL+Fl6v5dcU7qk7vbINclWOhvCe/uklKnXV2QU382x7g
qpbYxJiJvz24C6tgDMmE0pwEz6PiCbh1dkc8+9cdp37eBcFLCOXYQb27gqVVyVtu
xO0LLVXPCv48bGSwljOz0FRC3FolzWxzrZogM/i2b/lmehHJ3D4ejmINmIgtFJ9P
JNNCH4Oh5YEbaFFlNf2m7lCoSuQkOlLZcGeLoipK2XvhZJff6c1uxValh/Mx5dNB
5Mgd5cOZSSEhwf7mcE8C3SHVfjeNfZGIqlkwdY8lvAOjirAtj6Yl88sJOUID/Q/N
hU9D8IZy6+Bk2cJQwI/Gzr590VYvlSTI+6lXr//oBBECAwEAATANBgkqhkiG9w0B
AQsFAAOCAQEArSMO88AYT+9tPCl5lXtSRa0OycqKNlW58GujxIDuR8WX1eFmGSHQ
uijo5KPYUnqydZzAewGC8NvC9WcLwFltNZ9igXikUHiAHc1JLfW7+7SgKpwOUb02
rJkUkpPA/SmwkLSKYiR1prt5wgSulU1HPBESep05DfR8MCU5+KHkLyXDqtrbudJ4
lQd9dSKJFn+cSjUC5JNxCPHoIISe7hfGFMLkd0/tVfSIXLVOAZG4K6zExUdjyPi8
qEuPq6QCRyIJbYQc5HfnARgwK6GXHqkyLWlqK946Yz8VOba7Nan5uQ6xCjUMHw8Z
z/673o/3DCaQ9N6dWahNQ09a9ZH8U1X4iA==
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAv4WXq/l1xTuqTu9sg1yVY6G8J7+6SUqddXZBTfzbHuCqltjE
mIm/PbgLq2AMyYTSnATPo+IJuHV2Rzz71x2nft4FwUsI5dhBvbuCpVXJW27E7Qst
Vc8K/jxsZLCWM7PQVELcWiXNbHOtmiAz+LZv+WZ6EcncPh6OYg2YiC0Un08k00If
g6HlgRtoUWU1/abuUKhK5CQ6UtlwZ4uiKkrZe+Fkl9/pzW7FVqWH8zHl00HkyB3l
w5lJISHB/uZwTwLdIdV+N419kYiqWTB1jyW8A6OKsC2PpiXzywk5QgP9D82FT0Pw
hnLr4GTZwlDAj8bOvn3RVi+VJMj7qVev/+gEEQIDAQABAoIBADdoXsjSEtBMwqiz
e6FFV7LLR7P4M9ygSY2B+MKnNH1qYe/iJn4626jvZfDeiNSEKKoaejffXRCQaveR
HQrO+XYqpV+WZayZM+vAI7vRZb+d/DrX0PXSQEvtDy7SJ6Itk0fNUBKEfTmy/bZp
Op/pp9tvWkFrNNyD2o1jgY1j/WNY8g605m0oURJ9WQsMUu/Kzu+NMoaKTIoQGb3d
dP71F4KaTXHYxj3B0c+y0NedKbrvnBsP6XbEpgJBaXjtD9z+z/aMF6dmuvpkx7uY
qzwPMRw05QPyJ9x+1V/v4TytY5f596NgW2niVj77BunkZasTYIEX7bjByrlTeLdx
xvPRpAECgYEA5KkM/ORbhN1oaw9+tQxA48oG2DFqChBr+vc4NU4j5SNFn9ks5nHI
xdJNZ9k+bjVUkBP4m88Wd07SW9zXCL8Q5lczb+p5SWl/Pp7ltqaxpH17uzamsaIv
KIBkeJTOU5TuWdXiV5FY+ofK9ojyEaqX1tmylWnoVe4bIMRWXE5bMSkCgYEA1mvJ
snkNzPFG0RK7ikjsNxrhzE07+7RSnoM9WeW8y2lvQ9MjdR6eOgqnnlcdk2A7OVbf
culNgLc0qx/PxZ4BV+8yLLb1EBBGvuVG+x4a6H2mLHdFCJekByZHaQNs9ogVLvdv
3z8D59KknBUjtj9dCw90Z41yMM4kpWMG9yfSEKkCgYEAvuCvytwF2d/JrrV8nD3i
XUTkecymLEiRGysMbNMR+9F56XotlSEe7KQloa8kAnPaZ3uEaOxyYJ4X1D+B8fct
cFsSwTYGkVXTtr6GG/cDC8EEbL+uX1J382Nae54croEAh1WYYGkg0eJRd4PSLxUt
M1j/TuLd4/2j/7JmNR/j2CECgYBdB3MBHghgzKXe+/OmMbFazyz8SN4nfLsDzwkF
QenBj0MY+DhADkK0B/9lcYKBeJT5cbmMz7AykkolnK22nbETh9ILGG4GxCkNlchQ
F2WxTSKV1EF9Ut11xKPi6fuSksQuFmjRQTPelsOYfIt7/M3PiKsGapYKmsXHg8l3
3i0D0QKBgQCi+HNOaYqduxwjrj8h4eUbiwjID8DCNJ+jXsuGVa6jcsfFpdpivx2c
ytYSXuTXLRq0I3c1ChUOGQQeztJ5GtCPnXjLHHMf3f6yr7Pk56AUmUsaIlR1Q2Zo
gqpFD8zYD5UFc2KM7Y38YTh4j82uDzDvHBBFpli7dEmSn2WpcmzFag==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,441 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QDebug>
#include <QSignalSpy>
#include <QTimer>
#include <QtNetwork/QSslServer>
#include <QtNetwork/QSslKey>
#include "private/qtlsbackend_p.h"
class tst_QSslServer : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void testOneSuccessfulConnection();
void testSelfSignedCertificateRejectedByServer();
void testSelfSignedCertificateRejectedByClient();
#if QT_CONFIG(openssl)
void testHandshakeInterruptedOnError();
void testPreSharedKeyAuthenticationRequired();
#endif
private:
QString testDataDir;
bool isTestingOpenSsl = false;
QSslConfiguration selfSignedClientQSslConfiguration();
QSslConfiguration selfSignedServerQSslConfiguration();
QSslConfiguration createQSslConfiguration(QString keyFileName, QString certificateFileName);
};
class SslServerSpy : public QObject
{
Q_OBJECT
public:
SslServerSpy(QSslConfiguration &configuration);
QSslServer server;
QSignalSpy sslErrorsSpy;
QSignalSpy peerVerifyErrorSpy;
QSignalSpy errorOccurredSpy;
QSignalSpy pendingConnectionAvailableSpy;
QSignalSpy preSharedKeyAuthenticationRequiredSpy;
QSignalSpy alertSentSpy;
QSignalSpy alertReceivedSpy;
QSignalSpy handshakeInterruptedOnErrorSpy;
QSignalSpy startedEncryptionHandshakeSpy;
};
SslServerSpy::SslServerSpy(QSslConfiguration &configuration)
: server(),
sslErrorsSpy(&server, &QSslServer::sslErrors),
peerVerifyErrorSpy(&server, &QSslServer::peerVerifyError),
errorOccurredSpy(&server, &QSslServer::errorOccurred),
pendingConnectionAvailableSpy(&server, &QSslServer::pendingConnectionAvailable),
preSharedKeyAuthenticationRequiredSpy(&server,
&QSslServer::preSharedKeyAuthenticationRequired),
alertSentSpy(&server, &QSslServer::alertSent),
alertReceivedSpy(&server, &QSslServer::alertReceived),
handshakeInterruptedOnErrorSpy(&server, &QSslServer::handshakeInterruptedOnError),
startedEncryptionHandshakeSpy(&server, &QSslServer::startedEncryptionHandshake)
{
server.setSslConfiguration(configuration);
}
void tst_QSslServer::initTestCase()
{
testDataDir = QFileInfo(QFINDTESTDATA("certs")).absolutePath();
if (testDataDir.isEmpty())
testDataDir = QCoreApplication::applicationDirPath();
if (!testDataDir.endsWith(QLatin1String("/")))
testDataDir += QLatin1String("/");
const QString openSslBackend = QTlsBackend::builtinBackendNames[QTlsBackend::nameIndexOpenSSL];
const auto &tlsBackends = QSslSocket::availableBackends();
if (tlsBackends.contains(openSslBackend)) {
isTestingOpenSsl = true;
}
}
QSslConfiguration tst_QSslServer::selfSignedClientQSslConfiguration()
{
return createQSslConfiguration(testDataDir + "certs/selfsigned-client.key",
testDataDir + "certs/selfsigned-client.crt");
}
QSslConfiguration tst_QSslServer::selfSignedServerQSslConfiguration()
{
return createQSslConfiguration(testDataDir + "certs/selfsigned-server.key",
testDataDir + "certs/selfsigned-server.crt");
}
QSslConfiguration tst_QSslServer::createQSslConfiguration(QString keyFileName,
QString certificateFileName)
{
QSslConfiguration configuration(QSslConfiguration::defaultConfiguration());
QFile keyFile(keyFileName);
if (keyFile.open(QIODevice::ReadOnly)) {
QSslKey key(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
if (!key.isNull()) {
configuration.setPrivateKey(key);
} else {
qCritical() << "Could not parse key: " << keyFileName;
}
} else {
qCritical() << "Could not find key: " << keyFileName;
}
QList<QSslCertificate> localCert = QSslCertificate::fromPath(certificateFileName);
if (!localCert.isEmpty() && !localCert.first().isNull()) {
configuration.setLocalCertificate(localCert.first());
} else {
qCritical() << "Could not find certificate: " << certificateFileName;
}
return configuration;
}
void tst_QSslServer::testOneSuccessfulConnection()
{
// Setup server
QSslConfiguration serverConfiguration = selfSignedServerQSslConfiguration();
SslServerSpy server(serverConfiguration);
QVERIFY(server.server.listen());
// Check that all signal spys are valid
QVERIFY(server.sslErrorsSpy.isValid());
QVERIFY(server.peerVerifyErrorSpy.isValid());
QVERIFY(server.errorOccurredSpy.isValid());
QVERIFY(server.pendingConnectionAvailableSpy.isValid());
QVERIFY(server.preSharedKeyAuthenticationRequiredSpy.isValid());
QVERIFY(server.alertSentSpy.isValid());
QVERIFY(server.alertReceivedSpy.isValid());
QVERIFY(server.handshakeInterruptedOnErrorSpy.isValid());
QVERIFY(server.startedEncryptionHandshakeSpy.isValid());
// Check that no connections has occurred
QCOMPARE(server.sslErrorsSpy.count(), 0);
QCOMPARE(server.peerVerifyErrorSpy.count(), 0);
QCOMPARE(server.errorOccurredSpy.count(), 0);
QCOMPARE(server.pendingConnectionAvailableSpy.count(), 0);
QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.count(), 0);
QCOMPARE(server.alertSentSpy.count(), 0);
QCOMPARE(server.alertReceivedSpy.count(), 0);
QCOMPARE(server.handshakeInterruptedOnErrorSpy.count(), 0);
QCOMPARE(server.startedEncryptionHandshakeSpy.count(), 0);
// Connect client
QSslSocket client;
QSslConfiguration clientConfiguration = QSslConfiguration::defaultConfiguration();
client.setSslConfiguration(clientConfiguration);
client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(),
server.server.serverPort());
// Type of certificate error to expect
const auto certificateError =
isTestingOpenSsl ? QSslError::SelfSignedCertificate : QSslError::CertificateUntrusted;
// Expected errors
connect(&client, &QSslSocket::sslErrors,
[&certificateError, &client](const QList<QSslError> &errors) {
QCOMPARE(errors.size(), 2);
for (auto error : errors) {
QVERIFY(error.error() == certificateError
|| error.error() == QSslError::HostNameMismatch);
}
client.ignoreSslErrors();
});
QEventLoop loop;
int waitFor = 2;
connect(&client, &QSslSocket::encrypted, [&loop, &waitFor]() {
if (!--waitFor)
loop.quit();
});
connect(&server.server, &QTcpServer::pendingConnectionAvailable, [&loop, &waitFor]() {
if (!--waitFor)
loop.quit();
});
QTimer::singleShot(5000, &loop, SLOT(quit()));
loop.exec();
// Check that one encrypted connection has occurred without error
QCOMPARE(server.sslErrorsSpy.count(), 0);
QCOMPARE(server.peerVerifyErrorSpy.count(), 0);
QCOMPARE(server.errorOccurredSpy.count(), 0);
QCOMPARE(server.pendingConnectionAvailableSpy.count(), 1);
QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.count(), 0);
QCOMPARE(server.alertSentSpy.count(), 0);
QCOMPARE(server.alertReceivedSpy.count(), 0);
QCOMPARE(server.handshakeInterruptedOnErrorSpy.count(), 0);
QCOMPARE(server.startedEncryptionHandshakeSpy.count(), 1);
// Check client socket
QVERIFY(client.isEncrypted());
QCOMPARE(client.state(), QAbstractSocket::ConnectedState);
}
void tst_QSslServer::testSelfSignedCertificateRejectedByServer()
{
// Set up server that verifies client
QSslConfiguration serverConfiguration = selfSignedServerQSslConfiguration();
serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer);
SslServerSpy server(serverConfiguration);
QVERIFY(server.server.listen());
// Connect client
QSslSocket client;
QSslConfiguration clientConfiguration = selfSignedClientQSslConfiguration();
clientConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
client.setSslConfiguration(clientConfiguration);
client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(),
server.server.serverPort());
QEventLoop loop;
QObject::connect(&client, SIGNAL(disconnected()), &loop, SLOT(quit()));
QTimer::singleShot(5000, &loop, SLOT(quit()));
loop.exec();
// Check that one encrypted connection has failed
QCOMPARE(server.sslErrorsSpy.count(), 1);
QCOMPARE(server.peerVerifyErrorSpy.count(), 1);
QCOMPARE(server.errorOccurredSpy.count(), 1);
QCOMPARE(server.pendingConnectionAvailableSpy.count(), 0);
QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.count(), 0);
QCOMPARE(server.alertSentSpy.count(),
isTestingOpenSsl ? 1 : 0); // OpenSSL only signal
QCOMPARE(server.alertReceivedSpy.count(), 0);
QCOMPARE(server.handshakeInterruptedOnErrorSpy.count(), 0);
QCOMPARE(server.startedEncryptionHandshakeSpy.count(), 1);
// Type of certificate error to expect
const auto certificateError =
isTestingOpenSsl ? QSslError::SelfSignedCertificate : QSslError::CertificateUntrusted;
// Check the sslErrorsSpy
const auto sslErrorsSpyErrors =
qvariant_cast<QList<QSslError>>(qAsConst(server.sslErrorsSpy).first()[1]);
QCOMPARE(sslErrorsSpyErrors.size(), 1);
QCOMPARE(sslErrorsSpyErrors.first().error(), certificateError);
// Check the peerVerifyErrorSpy
const auto peerVerifyErrorSpyError =
qvariant_cast<QSslError>(qAsConst(server.peerVerifyErrorSpy).first()[1]);
QCOMPARE(peerVerifyErrorSpyError.error(), certificateError);
// Check client socket
QVERIFY(!client.isEncrypted());
QCOMPARE(client.state(), QAbstractSocket::UnconnectedState);
}
void tst_QSslServer::testSelfSignedCertificateRejectedByClient()
{
// Set up server without verification of client
QSslConfiguration serverConfiguration = selfSignedServerQSslConfiguration();
SslServerSpy server(serverConfiguration);
QVERIFY(server.server.listen());
// Connect client that authenticates server
QSslSocket client;
QSslConfiguration clientConfiguration = selfSignedClientQSslConfiguration();
if (isTestingOpenSsl) {
clientConfiguration.setHandshakeMustInterruptOnError(true);
QVERIFY(clientConfiguration.handshakeMustInterruptOnError());
}
client.setSslConfiguration(clientConfiguration);
QSignalSpy clientConnectedSpy(&client, SIGNAL(connected()));
QSignalSpy clientHostFoundSpy(&client, SIGNAL(hostFound()));
QSignalSpy clientDisconnectedSpy(&client, SIGNAL(disconnected()));
QSignalSpy clientConnectionEncryptedSpy(&client, SIGNAL(encrypted()));
QSignalSpy clientSslErrorsSpy(&client, SIGNAL(sslErrors(QList<QSslError>)));
QSignalSpy clientErrorOccurredSpy(&client, SIGNAL(errorOccurred(QAbstractSocket::SocketError)));
client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(),
server.server.serverPort());
QEventLoop loop;
QTimer::singleShot(1000, &loop, SLOT(quit()));
loop.exec();
// Type of socket error to expect
const auto socketError = isTestingOpenSsl
? QAbstractSocket::SocketError::SslHandshakeFailedError
: QAbstractSocket::SocketError::RemoteHostClosedError;
QTcpSocket *connection = server.server.nextPendingConnection();
if (connection == nullptr) {
// Client disconnected before connection accepted by server
QCOMPARE(server.sslErrorsSpy.count(), 0);
QCOMPARE(server.peerVerifyErrorSpy.count(), 0);
QCOMPARE(server.errorOccurredSpy.count(), 1); // Client rejected first
QCOMPARE(server.pendingConnectionAvailableSpy.count(), 0);
QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.count(), 0);
QCOMPARE(server.alertSentSpy.count(), 0);
QCOMPARE(server.alertReceivedSpy.count(),
isTestingOpenSsl ? 1 : 0); // OpenSSL only signal
QCOMPARE(server.handshakeInterruptedOnErrorSpy.count(), 0);
QCOMPARE(server.startedEncryptionHandshakeSpy.count(), 1);
const auto errrOccuredSpyError = qvariant_cast<QAbstractSocket::SocketError>(
qAsConst(server.errorOccurredSpy).first()[1]);
QCOMPARE(errrOccuredSpyError, socketError);
} else {
// Client disconnected after connection accepted by server
QCOMPARE(server.sslErrorsSpy.count(), 0);
QCOMPARE(server.peerVerifyErrorSpy.count(), 0);
QCOMPARE(server.errorOccurredSpy.count(), 0); // Server accepted first
QCOMPARE(server.pendingConnectionAvailableSpy.count(), 1);
QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.count(), 0);
QCOMPARE(server.alertSentSpy.count(), 0);
QCOMPARE(server.alertReceivedSpy.count(),
isTestingOpenSsl ? 1 : 0); // OpenSSL only signal
QCOMPARE(server.handshakeInterruptedOnErrorSpy.count(), 0);
QCOMPARE(server.startedEncryptionHandshakeSpy.count(), 1);
QCOMPARE(connection->state(), QAbstractSocket::UnconnectedState);
QCOMPARE(connection->error(), socketError);
auto sslConnection = qobject_cast<QSslSocket *>(connection);
QVERIFY(sslConnection);
QVERIFY(!sslConnection->isEncrypted());
}
// Check that client has rejected server
QCOMPARE(clientConnectedSpy.count(), 1);
QCOMPARE(clientHostFoundSpy.count(), 1);
QCOMPARE(clientDisconnectedSpy.count(), 1);
QCOMPARE(clientConnectionEncryptedSpy.count(), 0);
QCOMPARE(clientSslErrorsSpy.count(), isTestingOpenSsl ? 0 : 1);
QCOMPARE(clientErrorOccurredSpy.count(), 1);
// Check client socket
QVERIFY(!client.isEncrypted());
QCOMPARE(client.state(), QAbstractSocket::UnconnectedState);
}
#if QT_CONFIG(openssl)
void tst_QSslServer::testHandshakeInterruptedOnError()
{
if (!isTestingOpenSsl)
QSKIP("This test requires OpenSSL as the active TLS backend");
auto serverConfiguration = selfSignedServerQSslConfiguration();
serverConfiguration.setHandshakeMustInterruptOnError(true);
QVERIFY(serverConfiguration.handshakeMustInterruptOnError());
serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer);
SslServerSpy server(serverConfiguration);
server.server.listen();
QSslSocket client;
auto clientConfiguration = selfSignedClientQSslConfiguration();
clientConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
client.setSslConfiguration(clientConfiguration);
client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(),
server.server.serverPort());
QEventLoop loop;
QObject::connect(&client, SIGNAL(disconnected()), &loop, SLOT(quit()));
QTimer::singleShot(5000, &loop, SLOT(quit()));
loop.exec();
// Check that client certificate causes handshake interrupted signal to be emitted
QCOMPARE(server.sslErrorsSpy.count(), 0);
QCOMPARE(server.peerVerifyErrorSpy.count(), 0);
QCOMPARE(server.errorOccurredSpy.count(), 1);
QCOMPARE(server.pendingConnectionAvailableSpy.count(), 0);
QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.count(), 0);
QCOMPARE(server.alertSentSpy.count(), 1);
QCOMPARE(server.alertReceivedSpy.count(), 0);
QCOMPARE(server.handshakeInterruptedOnErrorSpy.count(), 1);
QCOMPARE(server.startedEncryptionHandshakeSpy.count(), 1);
}
void tst_QSslServer::testPreSharedKeyAuthenticationRequired()
{
if (!isTestingOpenSsl)
QSKIP("This test requires OpenSSL as the active TLS backend");
auto serverConfiguration = QSslConfiguration::defaultConfiguration();
serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer);
serverConfiguration.setProtocol(QSsl::TlsV1_2);
serverConfiguration.setCiphers({ QSslCipher("PSK-AES256-CBC-SHA") });
serverConfiguration.setPreSharedKeyIdentityHint("Server Y");
SslServerSpy server(serverConfiguration);
connect(&server.server, &QSslServer::preSharedKeyAuthenticationRequired,
[](QSslSocket *, QSslPreSharedKeyAuthenticator *authenticator) {
QCOMPARE(authenticator->identity(), QByteArray("Client X"));
authenticator->setPreSharedKey("123456");
});
server.server.listen();
QSslSocket client;
auto clientConfiguration = QSslConfiguration::defaultConfiguration();
clientConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
clientConfiguration.setProtocol(QSsl::TlsV1_2);
clientConfiguration.setCiphers({ QSslCipher("PSK-AES256-CBC-SHA") });
client.setSslConfiguration(clientConfiguration);
connect(&client, &QSslSocket::preSharedKeyAuthenticationRequired,
[](QSslPreSharedKeyAuthenticator *authenticator) {
QCOMPARE(authenticator->identityHint(), QByteArray("Server Y"));
authenticator->setPreSharedKey("123456");
authenticator->setIdentity("Client X");
});
client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(),
server.server.serverPort());
connect(&server.server, &QSslServer::sslErrors,
[](QSslSocket *socket, const QList<QSslError> &errors) {
for (auto error : errors) {
QCOMPARE(error.error(), QSslError::NoPeerCertificate);
}
socket->ignoreSslErrors();
});
QEventLoop loop;
QObject::connect(&client, SIGNAL(encrypted()), &loop, SLOT(quit()));
QTimer::singleShot(5000, &loop, SLOT(quit()));
loop.exec();
// Check that server is connected
QCOMPARE(server.sslErrorsSpy.count(), 1);
QCOMPARE(server.peerVerifyErrorSpy.count(), 1);
QCOMPARE(server.errorOccurredSpy.count(), 0);
QCOMPARE(server.pendingConnectionAvailableSpy.count(), 1);
QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.count(), 1);
QCOMPARE(server.alertSentSpy.count(), 0);
QCOMPARE(server.alertReceivedSpy.count(), 0);
QCOMPARE(server.handshakeInterruptedOnErrorSpy.count(), 0);
QCOMPARE(server.startedEncryptionHandshakeSpy.count(), 1);
// Check client socket
QVERIFY(client.isEncrypted());
QCOMPARE(client.state(), QAbstractSocket::ConnectedState);
}
#endif
QTEST_MAIN(tst_QSslServer)
#include "tst_qsslserver.moc"