From 5bc48a4443b5b4a3ab2e20c6c839305f698946ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Klitzing?= Date: Thu, 28 Apr 2016 17:13:57 +0200 Subject: Add support for TLS PSK (client and server) [ChangeLog][QWebSocket] It is now possible to use TLS PSK ciphersuites. Change-Id: I9e96669494cec5e6a4e076fe9f10fcd4ef6358a4 Reviewed-by: Liang Qi --- src/websockets/qsslserver.cpp | 1 + src/websockets/qsslserver_p.h | 2 + src/websockets/qwebsocket.cpp | 22 ++++ src/websockets/qwebsocket.h | 1 + src/websockets/qwebsocket_p.cpp | 3 + src/websockets/qwebsocketserver.cpp | 23 ++++ src/websockets/qwebsocketserver.h | 1 + src/websockets/qwebsocketserver_p.cpp | 2 + .../qwebsocketserver/tst_qwebsocketserver.cpp | 127 +++++++++++++++++++++ 9 files changed, 182 insertions(+) diff --git a/src/websockets/qsslserver.cpp b/src/websockets/qsslserver.cpp index 41db66a..5df59f7 100644 --- a/src/websockets/qsslserver.cpp +++ b/src/websockets/qsslserver.cpp @@ -119,6 +119,7 @@ void QSslServer::incomingConnection(qintptr socket) connect(pSslSocket, static_cast(&QSslSocket::sslErrors), this, &QSslServer::sslErrors); connect(pSslSocket, &QSslSocket::encrypted, this, &QSslServer::newEncryptedConnection); + connect(pSslSocket, &QSslSocket::preSharedKeyAuthenticationRequired, this, &QSslServer::preSharedKeyAuthenticationRequired); addPendingConnection(pSslSocket); diff --git a/src/websockets/qsslserver_p.h b/src/websockets/qsslserver_p.h index 538e3b5..d5e581a 100644 --- a/src/websockets/qsslserver_p.h +++ b/src/websockets/qsslserver_p.h @@ -54,6 +54,7 @@ #include #include #include +#include #include QT_BEGIN_NAMESPACE @@ -74,6 +75,7 @@ Q_SIGNALS: void sslErrors(const QList &errors); void peerVerifyError(const QSslError &error); void newEncryptedConnection(); + void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator); protected: virtual void incomingConnection(qintptr socket); diff --git a/src/websockets/qwebsocket.cpp b/src/websockets/qwebsocket.cpp index 90973d6..ba343e4 100644 --- a/src/websockets/qwebsocket.cpp +++ b/src/websockets/qwebsocket.cpp @@ -238,6 +238,28 @@ not been filled in with new information when the signal returns. \note You cannot use Qt::QueuedConnection when connecting to this signal, or calling QWebSocket::ignoreSslErrors() will have no effect. */ +/*! + \fn void QWebSocket::preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator) + \since 5.8 + + This signal is emitted if the SSL/TLS handshake negotiates a PSK + ciphersuite, and therefore a PSK authentication is then required. + + When using PSK, the client must send to the server 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 websocket and must not be + deleted by the application. + + \sa QSslPreSharedKeyAuthenticator + \sa QSslSocket::preSharedKeyAuthenticationRequired() +*/ /*! \fn void QWebSocket::pong(quint64 elapsedTime, const QByteArray &payload) diff --git a/src/websockets/qwebsocket.h b/src/websockets/qwebsocket.h index e6c2473..cbe53ca 100644 --- a/src/websockets/qwebsocket.h +++ b/src/websockets/qwebsocket.h @@ -142,6 +142,7 @@ Q_SIGNALS: #ifndef QT_NO_SSL void sslErrors(const QList &errors); + void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator); #endif private: diff --git a/src/websockets/qwebsocket_p.cpp b/src/websockets/qwebsocket_p.cpp index 188df33..74945f4 100644 --- a/src/websockets/qwebsocket_p.cpp +++ b/src/websockets/qwebsocket_p.cpp @@ -60,6 +60,7 @@ #ifndef QT_NO_SSL #include #include +#include #endif #include @@ -592,6 +593,8 @@ void QWebSocketPrivate::makeConnections(const QTcpSocket *pTcpSocket) #ifndef QT_NO_SSL const QSslSocket * const sslSocket = qobject_cast(pTcpSocket); if (sslSocket) { + QObject::connect(sslSocket, &QSslSocket::preSharedKeyAuthenticationRequired, q, + &QWebSocket::preSharedKeyAuthenticationRequired); QObject::connect(sslSocket, &QSslSocket::encryptedBytesWritten, q, &QWebSocket::bytesWritten); typedef void (QSslSocket:: *sslErrorSignalType)(const QList &); diff --git a/src/websockets/qwebsocketserver.cpp b/src/websockets/qwebsocketserver.cpp index ab5da31..f8ecdf2 100644 --- a/src/websockets/qwebsocketserver.cpp +++ b/src/websockets/qwebsocketserver.cpp @@ -192,6 +192,29 @@ \sa peerVerifyError() */ +/*! + \fn void QWebSocketServer::preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator) + \since 5.8 + + QWebSocketServer emits this signal when it negotiates a PSK ciphersuite, and + therefore a PSK authentication is then required. + + When using PSK, the client must send to the server 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 socket and must not be + deleted by the application. + + \sa QSslPreSharedKeyAuthenticator + \sa QSslSocket::preSharedKeyAuthenticationRequired() +*/ + /*! \enum QWebSocketServer::SslMode Indicates whether the server operates over wss (SecureMode) or ws (NonSecureMode) diff --git a/src/websockets/qwebsocketserver.h b/src/websockets/qwebsocketserver.h index 58a3d54..47113e4 100644 --- a/src/websockets/qwebsocketserver.h +++ b/src/websockets/qwebsocketserver.h @@ -128,6 +128,7 @@ Q_SIGNALS: #ifndef QT_NO_SSL void peerVerifyError(const QSslError &error); void sslErrors(const QList &errors); + void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator); #endif void closed(); }; diff --git a/src/websockets/qwebsocketserver_p.cpp b/src/websockets/qwebsocketserver_p.cpp index 288c05b..91bfafc 100644 --- a/src/websockets/qwebsocketserver_p.cpp +++ b/src/websockets/qwebsocketserver_p.cpp @@ -104,6 +104,8 @@ void QWebSocketServerPrivate::init() q_ptr, &QWebSocketServer::peerVerifyError); QObject::connect(pSslServer, &QSslServer::sslErrors, q_ptr, &QWebSocketServer::sslErrors); + QObject::connect(pSslServer, &QSslServer::preSharedKeyAuthenticationRequired, + q_ptr, &QWebSocketServer::preSharedKeyAuthenticationRequired); } #else qFatal("SSL not supported on this platform."); diff --git a/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp b/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp index fb3d5b8..699939f 100644 --- a/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp +++ b/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp @@ -28,6 +28,10 @@ #include #include #include +#ifndef QT_NO_OPENSSL +#include +#include +#endif #include #include #include @@ -43,6 +47,47 @@ Q_DECLARE_METATYPE(QWebSocketCorsAuthenticator *) Q_DECLARE_METATYPE(QSslError) #endif +#ifndef QT_NO_OPENSSL +// Use this cipher to force PSK key sharing. +static const QString PSK_CIPHER_WITHOUT_AUTH = QStringLiteral("PSK-AES256-CBC-SHA"); +static const QByteArray PSK_CLIENT_PRESHAREDKEY = QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f"); +static const QByteArray PSK_SERVER_IDENTITY_HINT = QByteArrayLiteral("QtTestServerHint"); +static const QByteArray PSK_CLIENT_IDENTITY = QByteArrayLiteral("Client_identity"); + +class PskProvider : public QObject +{ + Q_OBJECT + +public: + bool m_server = false; + QByteArray m_identity; + QByteArray m_psk; + +public slots: + void providePsk(QSslPreSharedKeyAuthenticator *authenticator) + { + QVERIFY(authenticator); + QCOMPARE(authenticator->identityHint(), PSK_SERVER_IDENTITY_HINT); + if (m_server) + QCOMPARE(authenticator->maximumIdentityLength(), 0); + else + QVERIFY(authenticator->maximumIdentityLength() > 0); + + QVERIFY(authenticator->maximumPreSharedKeyLength() > 0); + + if (!m_identity.isEmpty()) { + authenticator->setIdentity(m_identity); + QCOMPARE(authenticator->identity(), m_identity); + } + + if (!m_psk.isEmpty()) { + authenticator->setPreSharedKey(m_psk); + QCOMPARE(authenticator->preSharedKey(), m_psk); + } + } +}; +#endif + class tst_QWebSocketServer : public QObject { Q_OBJECT @@ -58,6 +103,7 @@ private Q_SLOTS: void tst_settersAndGetters(); void tst_listening(); void tst_connectivity(); + void tst_preSharedKey(); void tst_maxPendingConnections(); void tst_serverDestroyedWhileSocketConnected(); }; @@ -74,6 +120,9 @@ void tst_QWebSocketServer::init() qRegisterMetaType("QWebSocketCorsAuthenticator *"); #ifndef QT_NO_SSL qRegisterMetaType("QSslError"); +#ifndef QT_NO_OPENSSL + qRegisterMetaType(); +#endif #endif } @@ -268,6 +317,84 @@ void tst_QWebSocketServer::tst_connectivity() QCOMPARE(serverErrorSpy.count(), 0); } +void tst_QWebSocketServer::tst_preSharedKey() +{ +#ifndef QT_NO_OPENSSL + QWebSocketServer server(QString(), QWebSocketServer::SecureMode); + + bool cipherFound = false; + const QList supportedCiphers = QSslSocket::supportedCiphers(); + for (const QSslCipher &cipher : supportedCiphers) { + if (cipher.name() == PSK_CIPHER_WITHOUT_AUTH) { + cipherFound = true; + break; + } + } + + if (!cipherFound) + QSKIP("SSL implementation does not support the necessary cipher"); + + QSslCipher cipher(PSK_CIPHER_WITHOUT_AUTH); + QList list; + list << cipher; + + QSslConfiguration config = QSslConfiguration::defaultConfiguration(); + config.setCiphers(list); + config.setPeerVerifyMode(QSslSocket::VerifyNone); + config.setPreSharedKeyIdentityHint(PSK_SERVER_IDENTITY_HINT); + server.setSslConfiguration(config); + + PskProvider providerServer; + providerServer.m_server = true; + providerServer.m_identity = PSK_CLIENT_IDENTITY; + providerServer.m_psk = PSK_CLIENT_PRESHAREDKEY; + connect(&server, &QWebSocketServer::preSharedKeyAuthenticationRequired, &providerServer, &PskProvider::providePsk); + + QSignalSpy serverPskRequiredSpy(&server, &QWebSocketServer::preSharedKeyAuthenticationRequired); + QSignalSpy serverConnectionSpy(&server, &QWebSocketServer::newConnection); + QSignalSpy serverErrorSpy(&server, + SIGNAL(serverError(QWebSocketProtocol::CloseCode))); + QSignalSpy serverClosedSpy(&server, &QWebSocketServer::closed); + QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList))); + + QWebSocket socket; + QSslConfiguration socketConfig = QSslConfiguration::defaultConfiguration(); + socketConfig.setPeerVerifyMode(QSslSocket::VerifyNone); + socketConfig.setCiphers(list); + socket.setSslConfiguration(socketConfig); + + PskProvider providerClient; + providerClient.m_identity = PSK_CLIENT_IDENTITY; + providerClient.m_psk = PSK_CLIENT_PRESHAREDKEY; + connect(&socket, &QWebSocket::preSharedKeyAuthenticationRequired, &providerClient, &PskProvider::providePsk); + QSignalSpy socketPskRequiredSpy(&socket, &QWebSocket::preSharedKeyAuthenticationRequired); + QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected); + + QVERIFY(server.listen()); + QCOMPARE(server.serverAddress(), QHostAddress(QHostAddress::Any)); + QCOMPARE(server.serverUrl(), QUrl(QString::asprintf("wss://%ls:%d", + qUtf16Printable(QHostAddress(QHostAddress::LocalHost).toString()), server.serverPort()))); + + socket.open(server.serverUrl().toString()); + + if (socketConnectedSpy.count() == 0) + QVERIFY(socketConnectedSpy.wait()); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QCOMPARE(serverConnectionSpy.count(), 1); + QCOMPARE(serverPskRequiredSpy.count(), 1); + QCOMPARE(socketPskRequiredSpy.count(), 1); + + QCOMPARE(serverClosedSpy.count(), 0); + + server.close(); + + QVERIFY(serverClosedSpy.wait()); + QCOMPARE(serverClosedSpy.count(), 1); + QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(serverErrorSpy.count(), 0); +#endif +} + void tst_QWebSocketServer::tst_maxPendingConnections() { //tests if maximum connections are respected -- cgit v1.2.1