diff options
author | Øystein Heskestad <oystein.heskestad@qt.io> | 2022-05-03 15:58:44 +0200 |
---|---|---|
committer | Mårten Nordheim <marten.nordheim@qt.io> | 2022-06-04 02:22:56 +0200 |
commit | d631e581c0853cd94310a9377458c117edcbd65d (patch) | |
tree | 071b3fd3af39e0578162ce86218c7b25eed3d91e | |
parent | 782fbe0f63af5aeb583f84c28b7d0ff482bd28e4 (diff) |
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>
-rw-r--r-- | src/network/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/network/ssl/qsslserver.cpp | 287 | ||||
-rw-r--r-- | src/network/ssl/qsslserver.h | 58 | ||||
-rw-r--r-- | src/network/ssl/qsslserver_p.h | 37 | ||||
-rw-r--r-- | tests/auto/network/ssl/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslserver/CMakeLists.txt | 19 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslserver/certs/selfsigned-client.crt | 18 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslserver/certs/selfsigned-client.key | 27 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslserver/certs/selfsigned-server.crt | 18 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslserver/certs/selfsigned-server.key | 27 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslserver/tst_qsslserver.cpp | 441 |
11 files changed, 934 insertions, 0 deletions
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 9fc13f1a7e..98fcb5d166 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -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 diff --git a/src/network/ssl/qsslserver.cpp b/src/network/ssl/qsslserver.cpp new file mode 100644 index 0000000000..727d647784 --- /dev/null +++ b/src/network/ssl/qsslserver.cpp @@ -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 diff --git a/src/network/ssl/qsslserver.h b/src/network/ssl/qsslserver.h new file mode 100644 index 0000000000..d2f3abc456 --- /dev/null +++ b/src/network/ssl/qsslserver.h @@ -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 diff --git a/src/network/ssl/qsslserver_p.h b/src/network/ssl/qsslserver_p.h new file mode 100644 index 0000000000..b4b6490ed4 --- /dev/null +++ b/src/network/ssl/qsslserver_p.h @@ -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 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 <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" |