From 5bc48a4443b5b4a3ab2e20c6c839305f698946ed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Klitzing?= <aklitzing@gmail.com>
Date: Thu, 28 Apr 2016 17:13:57 +0200
Subject: [PATCH] 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 <liang.qi@qt.io>
---
 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 41db66a0..5df59f72 100644
--- a/src/websockets/qsslserver.cpp
+++ b/src/websockets/qsslserver.cpp
@@ -119,6 +119,7 @@ void QSslServer::incomingConnection(qintptr socket)
             connect(pSslSocket, static_cast<sslErrorsSignal>(&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 538e3b55..d5e581a5 100644
--- a/src/websockets/qsslserver_p.h
+++ b/src/websockets/qsslserver_p.h
@@ -54,6 +54,7 @@
 #include <QtNetwork/QTcpServer>
 #include <QtNetwork/QSslError>
 #include <QtNetwork/QSslConfiguration>
+#include <QtNetwork/QSslPreSharedKeyAuthenticator>
 #include <QtCore/QList>
 
 QT_BEGIN_NAMESPACE
@@ -74,6 +75,7 @@ Q_SIGNALS:
     void sslErrors(const QList<QSslError> &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 90973d69..ba343e43 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 e6c24732..cbe53ca0 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<QSslError> &errors);
+    void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator);
 #endif
 
 private:
diff --git a/src/websockets/qwebsocket_p.cpp b/src/websockets/qwebsocket_p.cpp
index 188df334..74945f4f 100644
--- a/src/websockets/qwebsocket_p.cpp
+++ b/src/websockets/qwebsocket_p.cpp
@@ -60,6 +60,7 @@
 #ifndef QT_NO_SSL
 #include <QtNetwork/QSslConfiguration>
 #include <QtNetwork/QSslError>
+#include <QtNetwork/QSslPreSharedKeyAuthenticator>
 #endif
 
 #include <QtCore/QDebug>
@@ -592,6 +593,8 @@ void QWebSocketPrivate::makeConnections(const QTcpSocket *pTcpSocket)
 #ifndef QT_NO_SSL
         const QSslSocket * const sslSocket = qobject_cast<const QSslSocket *>(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<QSslError> &);
diff --git a/src/websockets/qwebsocketserver.cpp b/src/websockets/qwebsocketserver.cpp
index ab5da310..f8ecdf25 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 58a3d540..47113e43 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<QSslError> &errors);
+    void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator);
 #endif
     void closed();
 };
diff --git a/src/websockets/qwebsocketserver_p.cpp b/src/websockets/qwebsocketserver_p.cpp
index 288c05bb..91bfafcd 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 fb3d5b8a..699939ff 100644
--- a/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp
+++ b/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp
@@ -28,6 +28,10 @@
 #include <QString>
 #include <QtTest>
 #include <QNetworkProxy>
+#ifndef QT_NO_OPENSSL
+#include <QtNetwork/qsslpresharedkeyauthenticator.h>
+#include <QtNetwork/qsslcipher.h>
+#endif
 #include <QtWebSockets/QWebSocketServer>
 #include <QtWebSockets/QWebSocket>
 #include <QtWebSockets/QWebSocketCorsAuthenticator>
@@ -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 *>("QWebSocketCorsAuthenticator *");
 #ifndef QT_NO_SSL
     qRegisterMetaType<QSslError>("QSslError");
+#ifndef QT_NO_OPENSSL
+    qRegisterMetaType<QSslPreSharedKeyAuthenticator *>();
+#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<QSslCipher> 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<QSslCipher> 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<QSslError>)));
+
+    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
-- 
GitLab