From d631e581c0853cd94310a9377458c117edcbd65d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20Heskestad?= Date: Tue, 3 May 2022 15:58:44 +0200 Subject: Unify QSslServer from QtWebSockets and QtHttpServer into QtNetwork MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Qt CI Bot --- tests/auto/network/ssl/CMakeLists.txt | 1 + tests/auto/network/ssl/qsslserver/CMakeLists.txt | 19 + .../ssl/qsslserver/certs/selfsigned-client.crt | 18 + .../ssl/qsslserver/certs/selfsigned-client.key | 27 ++ .../ssl/qsslserver/certs/selfsigned-server.crt | 18 + .../ssl/qsslserver/certs/selfsigned-server.key | 27 ++ .../auto/network/ssl/qsslserver/tst_qsslserver.cpp | 441 +++++++++++++++++++++ 7 files changed, 551 insertions(+) create mode 100644 tests/auto/network/ssl/qsslserver/CMakeLists.txt create mode 100644 tests/auto/network/ssl/qsslserver/certs/selfsigned-client.crt create mode 100644 tests/auto/network/ssl/qsslserver/certs/selfsigned-client.key create mode 100644 tests/auto/network/ssl/qsslserver/certs/selfsigned-server.crt create mode 100644 tests/auto/network/ssl/qsslserver/certs/selfsigned-server.key create mode 100644 tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp (limited to 'tests/auto/network/ssl') diff --git a/tests/auto/network/ssl/CMakeLists.txt b/tests/auto/network/ssl/CMakeLists.txt index 9c44e5c375..f6d231289d 100644 --- a/tests/auto/network/ssl/CMakeLists.txt +++ b/tests/auto/network/ssl/CMakeLists.txt @@ -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) diff --git a/tests/auto/network/ssl/qsslserver/CMakeLists.txt b/tests/auto/network/ssl/qsslserver/CMakeLists.txt new file mode 100644 index 0000000000..da1ccfd451 --- /dev/null +++ b/tests/auto/network/ssl/qsslserver/CMakeLists.txt @@ -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} +) 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..b13114cb47 --- /dev/null +++ b/tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp @@ -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 +#include +#include +#include + +#include +#include +#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 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 &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>(qAsConst(server.sslErrorsSpy).first()[1]); + QCOMPARE(sslErrorsSpyErrors.size(), 1); + QCOMPARE(sslErrorsSpyErrors.first().error(), certificateError); + + // Check the peerVerifyErrorSpy + const auto peerVerifyErrorSpyError = + qvariant_cast(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))); + 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( + 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(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 &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" -- cgit v1.2.3