summaryrefslogtreecommitdiffstats
path: root/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp')
-rw-r--r--tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp531
1 files changed, 531 insertions, 0 deletions
diff --git a/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp b/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp
new file mode 100644
index 0000000000..26d3a50a5b
--- /dev/null
+++ b/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp
@@ -0,0 +1,531 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#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
+ void plaintextClient();
+ void quietClient();
+ void twoGoodAndManyBadClients();
+
+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.size(), 0);
+ QCOMPARE(server.peerVerifyErrorSpy.size(), 0);
+ QCOMPARE(server.errorOccurredSpy.size(), 0);
+ QCOMPARE(server.pendingConnectionAvailableSpy.size(), 0);
+ QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.size(), 0);
+ QCOMPARE(server.alertSentSpy.size(), 0);
+ QCOMPARE(server.alertReceivedSpy.size(), 0);
+ QCOMPARE(server.handshakeInterruptedOnErrorSpy.size(), 0);
+ QCOMPARE(server.startedEncryptionHandshakeSpy.size(), 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.size(), 0);
+ QCOMPARE(server.peerVerifyErrorSpy.size(), 0);
+ QCOMPARE(server.errorOccurredSpy.size(), 0);
+ QCOMPARE(server.pendingConnectionAvailableSpy.size(), 1);
+ QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.size(), 0);
+ QCOMPARE(server.alertSentSpy.size(), 0);
+ QCOMPARE(server.alertReceivedSpy.size(), 0);
+ QCOMPARE(server.handshakeInterruptedOnErrorSpy.size(), 0);
+ QCOMPARE(server.startedEncryptionHandshakeSpy.size(), 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.size(), 1);
+ QCOMPARE(server.peerVerifyErrorSpy.size(), 1);
+ QCOMPARE(server.errorOccurredSpy.size(), 1);
+ QCOMPARE(server.pendingConnectionAvailableSpy.size(), 0);
+ QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.size(), 0);
+ QCOMPARE(server.alertSentSpy.size(),
+ isTestingOpenSsl ? 1 : 0); // OpenSSL only signal
+ QCOMPARE(server.alertReceivedSpy.size(), 0);
+ QCOMPARE(server.handshakeInterruptedOnErrorSpy.size(), 0);
+ QCOMPARE(server.startedEncryptionHandshakeSpy.size(), 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>>(std::as_const(server.sslErrorsSpy).first()[1]);
+ QCOMPARE(sslErrorsSpyErrors.size(), 1);
+ QCOMPARE(sslErrorsSpyErrors.first().error(), certificateError);
+
+ // Check the peerVerifyErrorSpy
+ const auto peerVerifyErrorSpyError =
+ qvariant_cast<QSslError>(std::as_const(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.size(), 0);
+ QCOMPARE(server.peerVerifyErrorSpy.size(), 0);
+ QCOMPARE(server.errorOccurredSpy.size(), 1); // Client rejected first
+ QCOMPARE(server.pendingConnectionAvailableSpy.size(), 0);
+ QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.size(), 0);
+ QCOMPARE(server.alertSentSpy.size(), 0);
+ QCOMPARE(server.alertReceivedSpy.size(),
+ isTestingOpenSsl ? 1 : 0); // OpenSSL only signal
+ QCOMPARE(server.handshakeInterruptedOnErrorSpy.size(), 0);
+ QCOMPARE(server.startedEncryptionHandshakeSpy.size(), 1);
+
+ const auto errrOccuredSpyError = qvariant_cast<QAbstractSocket::SocketError>(
+ std::as_const(server.errorOccurredSpy).first()[1]);
+ QCOMPARE(errrOccuredSpyError, socketError);
+ } else {
+ // Client disconnected after connection accepted by server
+ QCOMPARE(server.sslErrorsSpy.size(), 0);
+ QCOMPARE(server.peerVerifyErrorSpy.size(), 0);
+ QCOMPARE(server.errorOccurredSpy.size(), 0); // Server accepted first
+ QCOMPARE(server.pendingConnectionAvailableSpy.size(), 1);
+ QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.size(), 0);
+ QCOMPARE(server.alertSentSpy.size(), 0);
+ QCOMPARE(server.alertReceivedSpy.size(),
+ isTestingOpenSsl ? 1 : 0); // OpenSSL only signal
+ QCOMPARE(server.handshakeInterruptedOnErrorSpy.size(), 0);
+ QCOMPARE(server.startedEncryptionHandshakeSpy.size(), 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.size(), 1);
+ QCOMPARE(clientHostFoundSpy.size(), 1);
+ QCOMPARE(clientDisconnectedSpy.size(), 1);
+ QCOMPARE(clientConnectionEncryptedSpy.size(), 0);
+ QCOMPARE(clientSslErrorsSpy.size(), isTestingOpenSsl ? 0 : 1);
+ QCOMPARE(clientErrorOccurredSpy.size(), 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.size(), 0);
+ QCOMPARE(server.peerVerifyErrorSpy.size(), 0);
+ QCOMPARE(server.errorOccurredSpy.size(), 1);
+ QCOMPARE(server.pendingConnectionAvailableSpy.size(), 0);
+ QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.size(), 0);
+ QCOMPARE(server.alertSentSpy.size(), 1);
+ QCOMPARE(server.alertReceivedSpy.size(), 0);
+ QCOMPARE(server.handshakeInterruptedOnErrorSpy.size(), 1);
+ QCOMPARE(server.startedEncryptionHandshakeSpy.size(), 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.size(), 1);
+ QCOMPARE(server.peerVerifyErrorSpy.size(), 1);
+ QCOMPARE(server.errorOccurredSpy.size(), 0);
+ QCOMPARE(server.pendingConnectionAvailableSpy.size(), 1);
+ QCOMPARE(server.preSharedKeyAuthenticationRequiredSpy.size(), 1);
+ QCOMPARE(server.alertSentSpy.size(), 0);
+ QCOMPARE(server.alertReceivedSpy.size(), 0);
+ QCOMPARE(server.handshakeInterruptedOnErrorSpy.size(), 0);
+ QCOMPARE(server.startedEncryptionHandshakeSpy.size(), 1);
+
+ // Check client socket
+ QVERIFY(client.isEncrypted());
+ QCOMPARE(client.state(), QAbstractSocket::ConnectedState);
+}
+
+#endif
+
+void tst_QSslServer::plaintextClient()
+{
+ QSslConfiguration serverConfiguration = selfSignedServerQSslConfiguration();
+ SslServerSpy server(serverConfiguration);
+ QVERIFY(server.server.listen());
+
+ QTcpSocket socket;
+ QSignalSpy socketDisconnectedSpy(&socket, &QTcpSocket::disconnected);
+ socket.connectToHost(QHostAddress::LocalHost, server.server.serverPort());
+ QVERIFY(socket.waitForConnected());
+ QTest::qWait(100);
+ // No disconnect from short break...:
+ QCOMPARE(socket.state(), QAbstractSocket::SocketState::ConnectedState);
+
+ // ... but we write some plaintext data...:
+ socket.write("Hello World!");
+ socket.waitForBytesWritten();
+ // ... and quickly get disconnected:
+ QTRY_COMPARE_GT(socketDisconnectedSpy.size(), 0);
+ QCOMPARE(socket.state(), QAbstractSocket::SocketState::UnconnectedState);
+}
+
+void tst_QSslServer::quietClient()
+{
+ QSslConfiguration serverConfiguration = selfSignedServerQSslConfiguration();
+ SslServerSpy server(serverConfiguration);
+ server.server.setHandshakeTimeout(1'000);
+ QVERIFY(server.server.listen());
+
+ quint16 serverPeerPort = 0;
+ auto grabServerPeerPort = [&serverPeerPort](QSslSocket *socket) {
+ serverPeerPort = socket->peerPort();
+ };
+ QObject::connect(&server.server, &QSslServer::errorOccurred, &server.server,
+ grabServerPeerPort);
+
+ QTcpSocket socket;
+ QSignalSpy socketDisconnectedSpy(&socket, &QTcpSocket::disconnected);
+ socket.connectToHost(QHostAddress::LocalHost, server.server.serverPort());
+ quint16 clientLocalPort = socket.localPort();
+ QVERIFY(socket.waitForConnected());
+ // Disconnects after overlong break:
+ QVERIFY(socketDisconnectedSpy.wait(5'000));
+ QCOMPARE(socket.state(), QAbstractSocket::SocketState::UnconnectedState);
+
+ QCOMPARE_GT(server.errorOccurredSpy.size(), 0);
+ QCOMPARE(serverPeerPort, clientLocalPort);
+}
+
+void tst_QSslServer::twoGoodAndManyBadClients()
+{
+ QSslConfiguration serverConfiguration = selfSignedServerQSslConfiguration();
+ SslServerSpy server(serverConfiguration);
+ server.server.setHandshakeTimeout(750);
+ constexpr qsizetype ExpectedConnections = 5;
+ server.server.setMaxPendingConnections(ExpectedConnections);
+ QVERIFY(server.server.listen());
+
+ auto connectGoodClient = [&server](QSslSocket *socket) {
+ QObject::connect(socket, &QSslSocket::sslErrors, socket,
+ qOverload<const QList<QSslError> &>(&QSslSocket::ignoreSslErrors));
+ socket->connectToHostEncrypted("127.0.0.1", server.server.serverPort());
+ };
+ // Connect one socket encrypted so we have a socket in the regular queue
+ QSslSocket tlsSocket;
+ connectGoodClient(&tlsSocket);
+
+ // Then we connect a bunch of TCP sockets who will not send any data at all
+ std::array<QTcpSocket, size_t(ExpectedConnections) * 2> sockets;
+ for (QTcpSocket &socket : sockets)
+ socket.connectToHost(QHostAddress::LocalHost, server.server.serverPort());
+ QTest::qWait(500); // some leeway to let connections try to connect...
+
+ // I happen to know the sockets are all children of the server, so let's see
+ // how many are created:
+ qsizetype connectedCount = server.server.findChildren<QSslSocket *>().size();
+ QCOMPARE(connectedCount, ExpectedConnections);
+ // 1 socket is ready and pending
+ QCOMPARE(server.pendingConnectionAvailableSpy.size(), 1);
+
+ // Connect another client to make sure that the server is accepting connections again even after
+ // all the bad actors tried to connect:
+ QSslSocket goodClient;
+ connectGoodClient(&goodClient);
+ QTRY_COMPARE(server.pendingConnectionAvailableSpy.size(), 2);
+}
+
+QTEST_MAIN(tst_QSslServer)
+
+#include "tst_qsslserver.moc"