Commit 5bc48a44 authored by André Klitzing's avatar André Klitzing Committed by Liang Qi
Browse files

Add support for TLS PSK (client and server)


[ChangeLog][QWebSocket] It is now possible to use TLS PSK ciphersuites.

Change-Id: I9e96669494cec5e6a4e076fe9f10fcd4ef6358a4
Reviewed-by: default avatarLiang Qi <liang.qi@qt.io>
Showing with 182 additions and 0 deletions
......@@ -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);
......
......@@ -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);
......
......@@ -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)
......
......@@ -142,6 +142,7 @@ Q_SIGNALS:
#ifndef QT_NO_SSL
void sslErrors(const QList<QSslError> &errors);
void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator);
#endif
private:
......
......@@ -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> &);
......
......@@ -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)
......
......@@ -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();
};
......
......@@ -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.");
......
......@@ -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
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment