diff options
Diffstat (limited to 'tests/auto/network/ssl/qsslserver')
6 files changed, 650 insertions, 0 deletions
diff --git a/tests/auto/network/ssl/qsslserver/CMakeLists.txt b/tests/auto/network/ssl/qsslserver/CMakeLists.txt new file mode 100644 index 0000000000..5957b2720e --- /dev/null +++ b/tests/auto/network/ssl/qsslserver/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsslserver LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +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 + LIBRARIES + Qt::CorePrivate + Qt::NetworkPrivate + TESTDATA ${test_data} + BUNDLE_ANDROID_OPENSSL_LIBS +) diff --git a/tests/auto/network/ssl/qsslserver/certs/selfsigned-client.crt b/tests/auto/network/ssl/qsslserver/certs/selfsigned-client.crt new file mode 100644 index 0000000000..88da2db920 --- /dev/null +++ b/tests/auto/network/ssl/qsslserver/certs/selfsigned-client.crt @@ -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----- diff --git a/tests/auto/network/ssl/qsslserver/certs/selfsigned-client.key b/tests/auto/network/ssl/qsslserver/certs/selfsigned-client.key new file mode 100644 index 0000000000..9e59342963 --- /dev/null +++ b/tests/auto/network/ssl/qsslserver/certs/selfsigned-client.key @@ -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----- diff --git a/tests/auto/network/ssl/qsslserver/certs/selfsigned-server.crt b/tests/auto/network/ssl/qsslserver/certs/selfsigned-server.crt new file mode 100644 index 0000000000..c97d27721c --- /dev/null +++ b/tests/auto/network/ssl/qsslserver/certs/selfsigned-server.crt @@ -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----- diff --git a/tests/auto/network/ssl/qsslserver/certs/selfsigned-server.key b/tests/auto/network/ssl/qsslserver/certs/selfsigned-server.key new file mode 100644 index 0000000000..b7be118cb9 --- /dev/null +++ b/tests/auto/network/ssl/qsslserver/certs/selfsigned-server.key @@ -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----- 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" |