1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30
31#include <QtNetwork/qhostaddress.h>
32#include <QtNetwork/qsslsocket.h>
33#include <QtNetwork/qudpsocket.h>
34#include <QtNetwork/qdtls.h>
35
36#include <QtCore/qcryptographichash.h>
37#include <QtCore/qsharedpointer.h>
38#include <QtCore/qbytearray.h>
39#include <QtCore/qstring.h>
40#include <QtCore/qobject.h>
41#include <QtCore/qtimer.h>
42#include <QtCore/qdebug.h>
43
44#include <utility>
45#include <vector>
46
47QT_BEGIN_NAMESPACE
48
49#define STOP_ON_FAILURE \
50 if (QTest::currentTestFailed()) \
51 return;
52
53class tst_QDtlsCookie : public QObject
54{
55 Q_OBJECT
56
57public slots:
58 void initTestCase();
59 void init();
60
61private slots:
62 // Tests:
63 void construction();
64 void validateParameters_data();
65 void validateParameters();
66 void verifyClient();
67 void cookieGeneratorParameters();
68 void verifyMultipleClients();
69
70protected slots:
71 // Aux. functions:
72 void stopLoopOnMessage();
73 void serverReadyRead();
74 void clientReadyRead();
75 void handleClientTimeout();
76 void makeNoise();
77 void spawnClients();
78
79private:
80 void sendClientHello(QUdpSocket *socket, QDtls *handshake,
81 const QByteArray &serverMessage = {});
82 void receiveMessage(QUdpSocket *socket, QByteArray *message,
83 QHostAddress *address = nullptr,
84 quint16 *port = nullptr);
85
86 static QHostAddress toNonAny(const QHostAddress &addr);
87
88 enum AddressType
89 {
90 ValidAddress,
91 NullAddress,
92 BroadcastAddress,
93 MulticastAddress
94 };
95
96 QUdpSocket serverSocket;
97 QHostAddress serverAddress;
98 quint16 serverPort = 0;
99
100 QTestEventLoop testLoop;
101 int handshakeTimeoutMS = 500;
102
103 QDtlsClientVerifier listener;
104 using HandshakePtr = QSharedPointer<QDtls>;
105 HandshakePtr dtls;
106
107 const QCryptographicHash::Algorithm defaultHash =
108#ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
109 QCryptographicHash::Sha1;
110#else
111 QCryptographicHash::Sha256;
112#endif
113
114 using CookieParams = QDtlsClientVerifier::GeneratorParameters;
115
116 QUdpSocket noiseMaker;
117 QHostAddress spammerAddress;
118 QTimer noiseTimer;
119 quint16 spammerPort = 0;
120 const int noiseTimeoutMS = 5;
121
122 using SocketPtr = QSharedPointer<QUdpSocket>;
123 using ValidClient = QPair<SocketPtr, HandshakePtr>;
124 unsigned clientsToWait = 0;
125 unsigned clientsToAdd = 0;
126 std::vector<ValidClient> dtlsClients;
127 QTimer spawnTimer;
128};
129
130QHostAddress tst_QDtlsCookie::toNonAny(const QHostAddress &addr)
131{
132 if (addr == QHostAddress::Any || addr == QHostAddress::AnyIPv4)
133 return QHostAddress::LocalHost;
134 if (addr == QHostAddress::AnyIPv6)
135 return QHostAddress::LocalHostIPv6;
136 return addr;
137}
138
139void tst_QDtlsCookie::initTestCase()
140{
141 QVERIFY(noiseMaker.bind());
142 spammerAddress = toNonAny(addr: noiseMaker.localAddress());
143 spammerPort = noiseMaker.localPort();
144}
145
146void tst_QDtlsCookie::init()
147{
148 if (serverSocket.state() != QAbstractSocket::UnconnectedState) {
149 serverSocket.close();
150 // Disconnect stopLoopOnMessage or serverReadyRead slots:
151 serverSocket.disconnect();
152 }
153
154 QCOMPARE(serverSocket.state(), QAbstractSocket::UnconnectedState);
155 QVERIFY(serverSocket.bind());
156
157 serverAddress = toNonAny(addr: serverSocket.localAddress());
158 serverPort = serverSocket.localPort();
159
160 dtls.reset(t: new QDtls(QSslSocket::SslClientMode));
161 dtls->setPeer(address: serverAddress, port: serverPort);
162}
163
164void tst_QDtlsCookie::construction()
165{
166 QDtlsClientVerifier verifier;
167
168 QCOMPARE(verifier.dtlsError(), QDtlsError::NoError);
169 QCOMPARE(verifier.dtlsErrorString(), QString());
170 QCOMPARE(verifier.verifiedHello(), QByteArray());
171
172 const auto params = verifier.cookieGeneratorParameters();
173 QCOMPARE(params.hash, defaultHash);
174 QVERIFY(params.secret.size() > 0);
175}
176
177void tst_QDtlsCookie::validateParameters_data()
178{
179 QTest::addColumn<bool>(name: "invalidSocket");
180 QTest::addColumn<bool>(name: "emptyDatagram");
181 QTest::addColumn<int>(name: "addressType");
182
183 QTest::addRow(format: "socket") << true << false << int(ValidAddress);
184 QTest::addRow(format: "dgram") << false << true << int(ValidAddress);
185 QTest::addRow(format: "addr(invalid)") << false << false << int(NullAddress);
186 QTest::addRow(format: "addr(broadcast)") << false << false << int(BroadcastAddress);
187 QTest::addRow(format: "addr(multicast)") << false << false << int(MulticastAddress);
188
189 QTest::addRow(format: "socket-dgram") << true << true << int(ValidAddress);
190 QTest::addRow(format: "socket-dgram-addr(invalid)") << true << true << int(NullAddress);
191 QTest::addRow(format: "socket-dgram-addr(broadcast)") << true << true << int(BroadcastAddress);
192 QTest::addRow(format: "socket-dgram-addr(multicast)") << true << true << int(MulticastAddress);
193
194 QTest::addRow(format: "dgram-addr(invalid)") << false << true << int(NullAddress);
195 QTest::addRow(format: "dgram-addr(broadcast)") << false << true << int(BroadcastAddress);
196 QTest::addRow(format: "dgram-addr(multicast)") << false << true << int(MulticastAddress);
197
198 QTest::addRow(format: "socket-addr(invalid)") << true << false << int(NullAddress);
199 QTest::addRow(format: "socket-addr(broadcast)") << true << false << int(BroadcastAddress);
200 QTest::addRow(format: "socket-addr(multicast)") << true << false << int(MulticastAddress);
201}
202
203void tst_QDtlsCookie::validateParameters()
204{
205 connect(sender: &serverSocket, signal: &QUdpSocket::readyRead, receiver: this,
206 slot: &tst_QDtlsCookie::stopLoopOnMessage);
207
208 QFETCH(const bool, invalidSocket);
209 QFETCH(const bool, emptyDatagram);
210 QFETCH(const int, addressType);
211
212 QUdpSocket clientSocket;
213 QByteArray hello;
214 QHostAddress clientAddress;
215 quint16 clientPort = 0;
216
217 sendClientHello(socket: &clientSocket, handshake: dtls.data());
218 STOP_ON_FAILURE
219 receiveMessage(socket: &serverSocket, message: &hello, address: &clientAddress, port: &clientPort);
220 STOP_ON_FAILURE
221
222 switch (addressType) {
223 case MulticastAddress:
224 clientAddress.setAddress(QStringLiteral("224.0.0.0"));
225 break;
226 case BroadcastAddress:
227 clientAddress = QHostAddress::Broadcast;
228 break;
229 case NullAddress:
230 clientAddress = {};
231 break;
232 }
233
234 if (emptyDatagram)
235 hello.clear();
236
237 QUdpSocket *socket = invalidSocket ? nullptr : &serverSocket;
238 QCOMPARE(listener.verifyClient(socket, hello, clientAddress, clientPort), false);
239 QCOMPARE(listener.verifiedHello(), QByteArray());
240 QCOMPARE(listener.dtlsError(), QDtlsError::InvalidInputParameters);
241}
242
243void tst_QDtlsCookie::verifyClient()
244{
245 connect(sender: &serverSocket, signal: &QUdpSocket::readyRead, receiver: this,
246 slot: &tst_QDtlsCookie::stopLoopOnMessage);
247
248 QUdpSocket clientSocket;
249 connect(sender: &clientSocket, signal: &QUdpSocket::readyRead, receiver: this,
250 slot: &tst_QDtlsCookie::stopLoopOnMessage);
251
252 // Client: send an initial ClientHello message without any cookie:
253 sendClientHello(socket: &clientSocket, handshake: dtls.data());
254 STOP_ON_FAILURE
255 // Server: read the first ClientHello message:
256 QByteArray dgram;
257 QHostAddress clientAddress;
258 quint16 clientPort = 0;
259 receiveMessage(socket: &serverSocket, message: &dgram, address: &clientAddress, port: &clientPort);
260 STOP_ON_FAILURE
261 // Server: reply with a verify hello request (the client is not verified yet):
262 QCOMPARE(listener.verifyClient(&serverSocket, dgram, clientAddress, clientPort), false);
263 QCOMPARE(listener.verifiedHello(), QByteArray());
264 QCOMPARE(listener.dtlsError(), QDtlsError::NoError);
265 // Client: read hello verify request:
266 receiveMessage(socket: &clientSocket, message: &dgram);
267 STOP_ON_FAILURE
268 // Client: send a new hello message, this time with a cookie attached:
269 sendClientHello(socket: &clientSocket, handshake: dtls.data(), serverMessage: dgram);
270 STOP_ON_FAILURE
271 // Server: read a client-verified message:
272 receiveMessage(socket: &serverSocket, message: &dgram, address: &clientAddress, port: &clientPort);
273 STOP_ON_FAILURE
274 // Client's readyRead is not interesting anymore:
275 clientSocket.close();
276
277 // Verify with the address and port we extracted, do it twice (DTLS "listen"
278 // must be stateless and work as many times as needed):
279 for (int i = 0; i < 2; ++i) {
280 QCOMPARE(listener.verifyClient(&serverSocket, dgram, clientAddress, clientPort), true);
281 QCOMPARE(listener.verifiedHello(), dgram);
282 QCOMPARE(listener.dtlsError(), QDtlsError::NoError);
283 }
284
285 // Test that another freshly created (stateless) verifier can verify:
286 QDtlsClientVerifier anotherListener;
287 QCOMPARE(anotherListener.verifyClient(&serverSocket, dgram, clientAddress,
288 clientPort), true);
289 QCOMPARE(anotherListener.verifiedHello(), dgram);
290 QCOMPARE(anotherListener.dtlsError(), QDtlsError::NoError);
291
292 // Now, let's test if a DTLS server is able to create a new TLS session
293 // re-using the client's 'Hello' with a cookie inside:
294 QDtls session(QSslSocket::SslServerMode);
295 auto dtlsConf = QSslConfiguration::defaultDtlsConfiguration();
296 dtlsConf.setDtlsCookieVerificationEnabled(true);
297 session.setDtlsConfiguration(dtlsConf);
298 session.setPeer(address: clientAddress, port: clientPort);
299 // Borrow a secret and hash algorithm:
300 session.setCookieGeneratorParameters(listener.cookieGeneratorParameters());
301 // Trigger TLS state machine change to think it accepted a cookie and started
302 // a handshake:
303 QVERIFY(session.doHandshake(&serverSocket, dgram));
304
305 // Now let's use a wrong port:
306 QCOMPARE(listener.verifyClient(&serverSocket, dgram, clientAddress, serverPort), false);
307 // Invalid cookie, no verified hello message:
308 QCOMPARE(listener.verifiedHello(), QByteArray());
309 // But it's UDP so we ignore this "fishy datagram", no error expected:
310 QCOMPARE(listener.dtlsError(), QDtlsError::NoError);
311}
312
313void tst_QDtlsCookie::cookieGeneratorParameters()
314{
315 CookieParams params;// By defualt, 'secret' is empty.
316 QCOMPARE(listener.setCookieGeneratorParameters(params), false);
317 QCOMPARE(listener.dtlsError(), QDtlsError::InvalidInputParameters);
318 params.secret = "abcdefghijklmnopqrstuvwxyz";
319 QCOMPARE(listener.setCookieGeneratorParameters(params), true);
320 QCOMPARE(listener.dtlsError(), QDtlsError::NoError);
321}
322
323void tst_QDtlsCookie::verifyMultipleClients()
324{
325 // 'verifyClient' above was quite simple - it's like working with blocking
326 // sockets, step by step - we write, then make sure we read a datagram back
327 // etc. This test is more asynchronous - we are running an event loop and don't
328 // stop on the first datagram received, instead, we spawn many clients
329 // with which to exchange handshake messages and verify requests, while at
330 // the same time dealing with a 'noise maker' - a fake DTLS client, who keeps
331 // spamming our server with non-DTLS datagrams and initial ClientHello
332 // messages, but never replies to client verify requests.
333 connect(sender: &serverSocket, signal: &QUdpSocket::readyRead, receiver: this, slot: &tst_QDtlsCookie::serverReadyRead);
334
335 noiseTimer.setInterval(noiseTimeoutMS);
336 connect(sender: &noiseTimer, signal: &QTimer::timeout, receiver: this, slot: &tst_QDtlsCookie::makeNoise);
337
338 spawnTimer.setInterval(noiseTimeoutMS * 10);
339 connect(sender: &spawnTimer, signal: &QTimer::timeout, receiver: this, slot: &tst_QDtlsCookie::spawnClients);
340
341 noiseTimer.start();
342 spawnTimer.start();
343
344 clientsToAdd = clientsToWait = 100;
345
346 testLoop.enterLoopMSecs(ms: handshakeTimeoutMS * clientsToWait);
347 QVERIFY(!testLoop.timeout());
348 QVERIFY(clientsToWait == 0);
349}
350
351void tst_QDtlsCookie::sendClientHello(QUdpSocket *socket, QDtls *dtls,
352 const QByteArray &serverMessage)
353{
354 Q_ASSERT(socket && dtls);
355 dtls->doHandshake(socket, dgram: serverMessage);
356 // We don't really care about QDtls in this auto-test, but must be
357 // sure that we, indeed, sent our hello and if not - stop early without
358 // running event loop:
359 QCOMPARE(dtls->dtlsError(), QDtlsError::NoError);
360 // We never complete a handshake, so it must be 'HandshakeInProgress':
361 QCOMPARE(dtls->handshakeState(), QDtls::HandshakeInProgress);
362}
363
364void tst_QDtlsCookie::receiveMessage(QUdpSocket *socket, QByteArray *message,
365 QHostAddress *address, quint16 *port)
366{
367 Q_ASSERT(socket && message);
368
369 if (socket->pendingDatagramSize() <= 0)
370 testLoop.enterLoopMSecs(ms: handshakeTimeoutMS);
371
372 QVERIFY(!testLoop.timeout());
373 QVERIFY(socket->pendingDatagramSize());
374
375 message->resize(size: socket->pendingDatagramSize());
376 const qint64 read = socket->readDatagram(data: message->data(), maxlen: message->size(),
377 host: address, port);
378 QVERIFY(read > 0);
379
380 message->resize(size: read);
381 if (address)
382 QVERIFY(!address->isNull());
383}
384
385void tst_QDtlsCookie::stopLoopOnMessage()
386{
387 testLoop.exitLoop();
388}
389
390void tst_QDtlsCookie::serverReadyRead()
391{
392 Q_ASSERT(clientsToWait);
393
394 if (serverSocket.pendingDatagramSize() <= 0)
395 return;
396
397 QByteArray hello;
398 QHostAddress clientAddress;
399 quint16 clientPort = 0;
400
401 receiveMessage(socket: &serverSocket, message: &hello, address: &clientAddress, port: &clientPort);
402 if (QTest::currentTestFailed())
403 return testLoop.exitLoop();
404
405 const bool ok = listener.verifyClient(socket: &serverSocket, dgram: hello, address: clientAddress, port: clientPort);
406 if (listener.dtlsError() != QDtlsError::NoError) {
407 // exit early, let the test fail.
408 return testLoop.exitLoop();
409 }
410
411 if (!ok) // not verified yet.
412 return;
413
414 if (clientAddress == spammerAddress && clientPort == spammerPort) // should never happen
415 return testLoop.exitLoop();
416
417 --clientsToWait;
418 if (!clientsToWait) // done, success.
419 testLoop.exitLoop();
420}
421
422void tst_QDtlsCookie::clientReadyRead()
423{
424 QUdpSocket *clientSocket = qobject_cast<QUdpSocket *>(object: sender());
425 Q_ASSERT(clientSocket);
426
427 if (clientSocket->pendingDatagramSize() <= 0)
428 return;
429
430 QDtls *handshake = nullptr;
431 for (ValidClient &client : dtlsClients) {
432 if (client.first.data() == clientSocket) {
433 handshake = client.second.data();
434 break;
435 }
436 }
437
438 Q_ASSERT(handshake);
439
440 QByteArray response;
441 receiveMessage(socket: clientSocket, message: &response);
442 if (QTest::currentTestFailed() || !handshake->doHandshake(socket: clientSocket, dgram: response))
443 testLoop.exitLoop();
444}
445
446void tst_QDtlsCookie::makeNoise()
447{
448 noiseMaker.writeDatagram(datagram: {"Hello, my little DTLS server, take this useless dgram!"},
449 host: serverAddress, port: serverPort);
450 QDtls fakeHandshake(QSslSocket::SslClientMode);
451 fakeHandshake.setPeer(address: serverAddress, port: serverPort);
452 fakeHandshake.doHandshake(socket: &noiseMaker, dgram: {});
453}
454
455void tst_QDtlsCookie::spawnClients()
456{
457 for (int i = 0; i < 10 && clientsToAdd; ++i, --clientsToAdd) {
458 ValidClient newClient;
459 newClient.first.reset(t: new QUdpSocket);
460 connect(sender: newClient.first.data(), signal: &QUdpSocket::readyRead,
461 receiver: this, slot: &tst_QDtlsCookie::clientReadyRead);
462 newClient.second.reset(t: new QDtls(QSslSocket::SslClientMode));
463 newClient.second->setPeer(address: serverAddress, port: serverPort);
464 connect(sender: newClient.second.data(), signal: &QDtls::handshakeTimeout,
465 receiver: this, slot: &tst_QDtlsCookie::handleClientTimeout);
466 newClient.second->doHandshake(socket: newClient.first.data(), dgram: {});
467 dtlsClients.push_back(x: std::move(newClient));
468 }
469}
470
471void tst_QDtlsCookie::handleClientTimeout()
472{
473 QDtls *handshake = qobject_cast<QDtls *>(object: sender());
474 Q_ASSERT(handshake);
475
476 QUdpSocket *clientSocket = nullptr;
477 for (ValidClient &client : dtlsClients) {
478 if (client.second.data() == handshake) {
479 clientSocket = client.first.data();
480 break;
481 }
482 }
483
484 Q_ASSERT(clientSocket);
485 handshake->handleTimeout(socket: clientSocket);
486}
487
488QT_END_NAMESPACE
489
490QTEST_MAIN(tst_QDtlsCookie)
491
492#include "tst_qdtlscookie.moc"
493

source code of qtbase/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp