// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../shared/tlshelpers.h" #include QT_BEGIN_NAMESPACE namespace { bool dtlsErrorIsCleared(const QDtls &dtls) { return dtls.dtlsError() == QDtlsError::NoError && dtls.dtlsErrorString().isEmpty(); } using DtlsPtr = QScopedPointer; bool dtlsErrorIsCleared(DtlsPtr &dtls) { return dtlsErrorIsCleared(*dtls); } } // unnamed namespace #define QDTLS_VERIFY_NO_ERROR(obj) QVERIFY(dtlsErrorIsCleared(obj)) #define QDTLS_VERIFY_HANDSHAKE_SUCCESS(obj) \ QVERIFY(obj->isConnectionEncrypted()); \ QCOMPARE(obj->handshakeState(), QDtls::HandshakeComplete); \ QDTLS_VERIFY_NO_ERROR(obj); \ QCOMPARE(obj->peerVerificationErrors().size(), 0) class tst_QDtls : public QObject { Q_OBJECT public slots: void initTestCase(); void init(); private slots: // Tests: void construction_data(); void construction(); void configuration_data(); void configuration(); void invalidConfiguration(); void setPeer_data(); void setPeer(); void handshake_data(); void handshake(); void handshakeWithRetransmission(); void sessionCipher(); void cipherPreferences_data(); void cipherPreferences(); void protocolVersionMatching_data(); void protocolVersionMatching(); void verificationErrors_data(); void verificationErrors(); void presetExpectedErrors_data(); void presetExpectedErrors(); void verifyServerCertificate_data(); void verifyServerCertificate(); void verifyClientCertificate_data(); void verifyClientCertificate(); void blacklistedCerificate(); void readWriteEncrypted_data(); void readWriteEncrypted(); void datagramFragmentation(); protected slots: void handshakeReadyRead(); void encryptedReadyRead(); void pskRequested(QSslPreSharedKeyAuthenticator *auth); void handleHandshakeTimeout(); private: void clientServerData(); void connectHandshakeReadingSlots(); void connectEncryptedReadingSlots(); bool verificationErrorDetected(QSslError::SslError code) const; static QHostAddress toNonAny(const QHostAddress &addr); QUdpSocket serverSocket; QHostAddress serverAddress; quint16 serverPort = 0; QSslConfiguration defaultServerConfig; QSslCertificate selfSignedCert; QString hostName; QSslKey serverKeySS; bool serverDropDgram = false; const QByteArray serverExpectedPlainText = "Hello W ... hmm, I mean DTLS server!"; QByteArray serverReceivedPlainText; QUdpSocket clientSocket; QHostAddress clientAddress; quint16 clientPort = 0; bool clientDropDgram = false; const QByteArray clientExpectedPlainText = "Hello DTLS client."; QByteArray clientReceivedPlainText; DtlsPtr serverCrypto; DtlsPtr clientCrypto; QTestEventLoop testLoop; const int handshakeTimeoutMS = 5000; const int dataExchangeTimeoutMS = 1000; const QByteArray presharedKey = "DEADBEEFDEADBEEF"; QString certDirPath; }; QT_END_NAMESPACE Q_DECLARE_METATYPE(QSsl::SslProtocol) Q_DECLARE_METATYPE(QSslSocket::SslMode) Q_DECLARE_METATYPE(QSslSocket::PeerVerifyMode) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QSslKey) QT_BEGIN_NAMESPACE void qt_ForceTlsSecurityLevel(); void tst_QDtls::initTestCase() { if (!TlsAux::classImplemented(QSsl::ImplementedClass::Dtls)) QSKIP("The active TLS backend does not support DTLS"); certDirPath = QFileInfo(QFINDTESTDATA("certs")).absolutePath(); QVERIFY(certDirPath.size() > 0); certDirPath += QDir::separator() + QStringLiteral("certs") + QDir::separator(); QVERIFY(QSslSocket::supportsSsl()); QFile keyFile(certDirPath + QStringLiteral("ss-srv-key.pem")); QVERIFY(keyFile.open(QIODevice::ReadOnly)); serverKeySS = QSslKey(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, "foobar"); QVERIFY(!serverKeySS.isNull()); QList certificates = QSslCertificate::fromPath(certDirPath + QStringLiteral("ss-srv-cert.pem")); QVERIFY(!certificates.isEmpty()); QVERIFY(!certificates.first().isNull()); selfSignedCert = certificates.first(); defaultServerConfig = QSslConfiguration::defaultDtlsConfiguration(); defaultServerConfig.setPeerVerifyMode(QSslSocket::VerifyNone); defaultServerConfig.setDtlsCookieVerificationEnabled(false); hostName = QStringLiteral("bob.org"); qt_ForceTlsSecurityLevel(); } void tst_QDtls::init() { if (serverSocket.state() != QAbstractSocket::UnconnectedState) { serverSocket.close(); // disconnect signals/slots: serverSocket.disconnect(); } QVERIFY(serverSocket.bind()); serverAddress = toNonAny(serverSocket.localAddress()); serverPort = serverSocket.localPort(); if (clientSocket.localPort()) { clientSocket.close(); // disconnect signals/slots: clientSocket.disconnect(); } clientAddress = {}; clientPort = 0; serverCrypto.reset(new QDtls(QSslSocket::SslServerMode)); serverDropDgram = false; serverReceivedPlainText.clear(); clientCrypto.reset(new QDtls(QSslSocket::SslClientMode)); clientDropDgram = false; clientReceivedPlainText.clear(); connect(clientCrypto.data(), &QDtls::handshakeTimeout, this, &tst_QDtls::handleHandshakeTimeout); connect(serverCrypto.data(), &QDtls::handshakeTimeout, this, &tst_QDtls::handleHandshakeTimeout); } void tst_QDtls::construction_data() { clientServerData(); } void tst_QDtls::construction() { QFETCH(const QSslSocket::SslMode, mode); QDtls dtls(mode); QCOMPARE(dtls.peerAddress(), QHostAddress()); QCOMPARE(dtls.peerPort(), quint16()); QCOMPARE(dtls.peerVerificationName(), QString()); QCOMPARE(dtls.sslMode(), mode); QCOMPARE(dtls.mtuHint(), quint16()); const auto params = dtls.cookieGeneratorParameters(); QVERIFY(params.secret.size() > 0); #ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 QCOMPARE(params.hash, QCryptographicHash::Sha1); #else QCOMPARE(params.hash, QCryptographicHash::Sha256); #endif QCOMPARE(dtls.dtlsConfiguration(), QSslConfiguration::defaultDtlsConfiguration()); QCOMPARE(dtls.handshakeState(), QDtls::HandshakeNotStarted); QCOMPARE(dtls.isConnectionEncrypted(), false); QCOMPARE(dtls.sessionCipher(), QSslCipher()); QCOMPARE(dtls.sessionProtocol(), QSsl::UnknownProtocol); QCOMPARE(dtls.dtlsError(), QDtlsError::NoError); QCOMPARE(dtls.dtlsErrorString(), QString()); QCOMPARE(dtls.peerVerificationErrors().size(), 0); } void tst_QDtls::configuration_data() { clientServerData(); } void tst_QDtls::configuration() { // There is a proper auto-test for QSslConfiguration in our TLS test suite, // here we only test several DTLS-related details. auto config = QSslConfiguration::defaultDtlsConfiguration(); QCOMPARE(config.protocol(), QSsl::DtlsV1_2OrLater); const QList ciphers = config.ciphers(); QVERIFY(ciphers.size() > 0); for (const auto &cipher : ciphers) QVERIFY(cipher.usedBits() >= 128); QCOMPARE(config.dtlsCookieVerificationEnabled(), true); QFETCH(const QSslSocket::SslMode, mode); QDtls dtls(mode); QCOMPARE(dtls.dtlsConfiguration(), config); config.setProtocol(QSsl::DtlsV1_2); config.setDtlsCookieVerificationEnabled(false); QCOMPARE(config.dtlsCookieVerificationEnabled(), false); QVERIFY(dtls.setDtlsConfiguration(config)); QDTLS_VERIFY_NO_ERROR(dtls); QCOMPARE(dtls.dtlsConfiguration(), config); if (mode == QSslSocket::SslClientMode) { // Testing a DTLS server would be more complicated, we'd need a DTLS // client sending ClientHello(s), running an event loop etc. - way too // much dancing for a simple setter/getter test. QVERIFY(dtls.setPeer(serverAddress, serverPort)); QDTLS_VERIFY_NO_ERROR(dtls); QUdpSocket clientSocket; QVERIFY(dtls.doHandshake(&clientSocket)); QDTLS_VERIFY_NO_ERROR(dtls); QCOMPARE(dtls.handshakeState(), QDtls::HandshakeInProgress); // As soon as handshake started, it's not allowed to change configuration: QVERIFY(!dtls.setDtlsConfiguration(QSslConfiguration::defaultDtlsConfiguration())); QCOMPARE(dtls.dtlsError(), QDtlsError::InvalidOperation); QCOMPARE(dtls.dtlsConfiguration(), config); } static bool doneAlready = false; if (!doneAlready) { doneAlready = true; QSslConfiguration nullConfig; const auto defaultDtlsConfig = QSslConfiguration::defaultDtlsConfiguration(); const auto restoreDefault = qScopeGuard([&defaultDtlsConfig] { QSslConfiguration::setDefaultDtlsConfiguration(defaultDtlsConfig); }); QSslConfiguration::setDefaultDtlsConfiguration(nullConfig); QCOMPARE(QSslConfiguration::defaultDtlsConfiguration(), nullConfig); QVERIFY(QSslConfiguration::defaultDtlsConfiguration() != defaultDtlsConfig); } } void tst_QDtls::invalidConfiguration() { QUdpSocket socket; QDtls crypto(QSslSocket::SslClientMode); QVERIFY(crypto.setPeer(serverAddress, serverPort)); // Note: not defaultDtlsConfiguration(), so the protocol is TLS (without D): QVERIFY(crypto.setDtlsConfiguration(QSslConfiguration::defaultConfiguration())); QDTLS_VERIFY_NO_ERROR(crypto); QCOMPARE(crypto.dtlsConfiguration(), QSslConfiguration::defaultConfiguration()); // Try to start the handshake: QCOMPARE(crypto.doHandshake(&socket), false); QCOMPARE(crypto.dtlsError(), QDtlsError::TlsInitializationError); } void tst_QDtls::setPeer_data() { clientServerData(); } void tst_QDtls::setPeer() { static const QHostAddress invalid[] = {QHostAddress(), QHostAddress(QHostAddress::Broadcast), QHostAddress(QStringLiteral("224.0.0.0"))}; static const QString peerName = QStringLiteral("does not matter actually"); QFETCH(const QSslSocket::SslMode, mode); QDtls dtls(mode); for (const auto &addr : invalid) { QCOMPARE(dtls.setPeer(addr, 100, peerName), false); QCOMPARE(dtls.dtlsError(), QDtlsError::InvalidInputParameters); QCOMPARE(dtls.peerAddress(), QHostAddress()); QCOMPARE(dtls.peerPort(), quint16()); QCOMPARE(dtls.peerVerificationName(), QString()); } QVERIFY(dtls.setPeer(serverAddress, serverPort, peerName)); QDTLS_VERIFY_NO_ERROR(dtls); QCOMPARE(dtls.peerAddress(), serverAddress); QCOMPARE(dtls.peerPort(), serverPort); QCOMPARE(dtls.peerVerificationName(), peerName); if (mode == QSslSocket::SslClientMode) { // We test for client mode only, for server mode we'd have to run event // loop etc. too much work for a simple setter/getter test. QUdpSocket clientSocket; QVERIFY(dtls.doHandshake(&clientSocket)); QDTLS_VERIFY_NO_ERROR(dtls); QCOMPARE(dtls.handshakeState(), QDtls::HandshakeInProgress); QCOMPARE(dtls.setPeer(serverAddress, serverPort), false); QCOMPARE(dtls.dtlsError(), QDtlsError::InvalidOperation); } } void tst_QDtls::handshake_data() { QTest::addColumn("withCertificate"); QTest::addRow("no-cert") << false; QTest::addRow("with-cert") << true; } void tst_QDtls::handshake() { connectHandshakeReadingSlots(); QFETCH(const bool, withCertificate); auto serverConfig = defaultServerConfig; auto clientConfig = QSslConfiguration::defaultDtlsConfiguration(); if (!withCertificate) { connect(serverCrypto.data(), &QDtls::pskRequired, this, &tst_QDtls::pskRequested); connect(clientCrypto.data(), &QDtls::pskRequired, this, &tst_QDtls::pskRequested); clientConfig.setPeerVerifyMode(QSslSocket::VerifyNone); QVERIFY(clientConfig.peerCertificate().isNull()); } else { serverConfig.setPrivateKey(serverKeySS); serverConfig.setLocalCertificate(selfSignedCert); clientConfig.setCaCertificates({selfSignedCert}); } QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); QVERIFY(clientCrypto->setDtlsConfiguration(clientConfig)); // Some early checks before we run event loop. // Remote was not set yet: QVERIFY(!clientCrypto->doHandshake(&clientSocket)); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::InvalidOperation); QVERIFY(!serverCrypto->doHandshake(&serverSocket, QByteArray("ClientHello"))); QCOMPARE(serverCrypto->dtlsError(), QDtlsError::InvalidOperation); QVERIFY(clientCrypto->setPeer(serverAddress, serverPort, hostName)); // Invalid socket: QVERIFY(!clientCrypto->doHandshake(nullptr)); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::InvalidInputParameters); // Now we are ready for handshake: QVERIFY(clientCrypto->doHandshake(&clientSocket)); QDTLS_VERIFY_NO_ERROR(clientCrypto); QCOMPARE(clientCrypto->handshakeState(), QDtls::HandshakeInProgress); testLoop.enterLoopMSecs(handshakeTimeoutMS); QVERIFY(!testLoop.timeout()); QVERIFY(serverCrypto->isConnectionEncrypted()); QDTLS_VERIFY_NO_ERROR(serverCrypto); QCOMPARE(serverCrypto->handshakeState(), QDtls::HandshakeComplete); QCOMPARE(serverCrypto->peerVerificationErrors().size(), 0); QVERIFY(clientCrypto->isConnectionEncrypted()); QDTLS_VERIFY_NO_ERROR(clientCrypto); QCOMPARE(clientCrypto->handshakeState(), QDtls::HandshakeComplete); QCOMPARE(clientCrypto->peerVerificationErrors().size(), 0); if (withCertificate) { const auto serverCert = clientCrypto->dtlsConfiguration().peerCertificate(); QVERIFY(!serverCert.isNull()); QCOMPARE(serverCert, selfSignedCert); } // Already in 'HandshakeComplete' state/encrypted. QVERIFY(!clientCrypto->doHandshake(&clientSocket)); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::InvalidOperation); QVERIFY(!serverCrypto->doHandshake(&serverSocket, {"ServerHello"})); QCOMPARE(serverCrypto->dtlsError(), QDtlsError::InvalidOperation); // Cannot change a remote without calling shutdown first. QVERIFY(!clientCrypto->setPeer(serverAddress, serverPort)); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::InvalidOperation); QVERIFY(!serverCrypto->setPeer(clientAddress, clientPort)); QCOMPARE(serverCrypto->dtlsError(), QDtlsError::InvalidOperation); } void tst_QDtls::handshakeWithRetransmission() { connectHandshakeReadingSlots(); auto serverConfig = defaultServerConfig; serverConfig.setPrivateKey(serverKeySS); serverConfig.setLocalCertificate(selfSignedCert); QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); auto clientConfig = QSslConfiguration::defaultDtlsConfiguration(); clientConfig.setCaCertificates({selfSignedCert}); QVERIFY(clientCrypto->setDtlsConfiguration(clientConfig)); QVERIFY(clientCrypto->setPeer(serverAddress, serverPort, hostName)); // Now we are ready for handshake: QVERIFY(clientCrypto->doHandshake(&clientSocket)); QDTLS_VERIFY_NO_ERROR(clientCrypto); QCOMPARE(clientCrypto->handshakeState(), QDtls::HandshakeInProgress); serverDropDgram = true; clientDropDgram = true; // Every failed re-transmission doubles the next timeout. We don't want to // slow down the test just to check the re-transmission ability, so we'll // drop only the first 'ClientHello' and 'ServerHello' datagrams. The // arithmetic is approximately this: the first ClientHello to be dropped - // client will re-transmit in 1s., the first part of 'ServerHello' to be // dropped, the client then will re-transmit after another 2 s. Thus it's ~3. // We err on safe side and double our (already quite generous) 5s. testLoop.enterLoopMSecs(handshakeTimeoutMS * 2); QVERIFY(!testLoop.timeout()); QDTLS_VERIFY_HANDSHAKE_SUCCESS(serverCrypto); QDTLS_VERIFY_HANDSHAKE_SUCCESS(clientCrypto); } void tst_QDtls::sessionCipher() { connectHandshakeReadingSlots(); auto serverConfig = defaultServerConfig; serverConfig.setPrivateKey(serverKeySS); serverConfig.setLocalCertificate(selfSignedCert); QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); auto clientConfig = QSslConfiguration::defaultDtlsConfiguration(); clientConfig.setCaCertificates({selfSignedCert}); QVERIFY(clientCrypto->setDtlsConfiguration(clientConfig)); QVERIFY(clientCrypto->setPeer(serverAddress, serverPort, hostName)); QVERIFY(clientCrypto->doHandshake(&clientSocket)); testLoop.enterLoopMSecs(handshakeTimeoutMS); QVERIFY(!testLoop.timeout()); QDTLS_VERIFY_HANDSHAKE_SUCCESS(clientCrypto); QDTLS_VERIFY_HANDSHAKE_SUCCESS(serverCrypto); const auto defaultDtlsConfig = QSslConfiguration::defaultDtlsConfiguration(); const auto clCipher = clientCrypto->sessionCipher(); QVERIFY(!clCipher.isNull()); QVERIFY(defaultDtlsConfig.ciphers().contains(clCipher)); const auto srvCipher = serverCrypto->sessionCipher(); QVERIFY(!srvCipher.isNull()); QVERIFY(defaultDtlsConfig.ciphers().contains(srvCipher)); QCOMPARE(clCipher, srvCipher); } void tst_QDtls::cipherPreferences_data() { QTest::addColumn("preferClient"); QTest::addRow("prefer-server") << true; QTest::addRow("prefer-client") << false; } void tst_QDtls::cipherPreferences() { // This test is based on the similar case in tst_QSslSocket. We test it for QDtls // because it's possible to set ciphers and corresponding ('server preferred') // options via QSslConfiguration. const QSslCipher aes128(QStringLiteral("AES128-SHA")); const QSslCipher aes256(QStringLiteral("AES256-SHA")); auto serverConfig = defaultServerConfig; const QList ciphers = serverConfig.ciphers(); if (!ciphers.contains(aes128) || !ciphers.contains(aes256)) QSKIP("The ciphers needed by this test were not found in the default DTLS configuration"); serverConfig.setCiphers({aes128, aes256}); serverConfig.setLocalCertificate(selfSignedCert); serverConfig.setPrivateKey(serverKeySS); QFETCH(const bool, preferClient); if (preferClient) serverConfig.setSslOption(QSsl::SslOptionDisableServerCipherPreference, true); QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); QDTLS_VERIFY_NO_ERROR(serverCrypto); auto clientConfig = QSslConfiguration::defaultDtlsConfiguration(); clientConfig.setPeerVerifyMode(QSslSocket::VerifyNone); clientConfig.setCiphers({aes256, aes128}); QVERIFY(clientCrypto->setDtlsConfiguration(clientConfig)); QVERIFY(clientCrypto->setPeer(serverAddress, serverPort)); QDTLS_VERIFY_NO_ERROR(clientCrypto); connectHandshakeReadingSlots(); QVERIFY(clientCrypto->doHandshake(&clientSocket)); QDTLS_VERIFY_NO_ERROR(clientCrypto); testLoop.enterLoopMSecs(handshakeTimeoutMS); QVERIFY(!testLoop.timeout()); QDTLS_VERIFY_HANDSHAKE_SUCCESS(clientCrypto); QDTLS_VERIFY_HANDSHAKE_SUCCESS(serverCrypto); if (preferClient) { QCOMPARE(clientCrypto->sessionCipher(), aes256); QCOMPARE(serverCrypto->sessionCipher(), aes256); } else { QCOMPARE(clientCrypto->sessionCipher(), aes128); QCOMPARE(serverCrypto->sessionCipher(), aes128); } } void tst_QDtls::protocolVersionMatching_data() { QTest::addColumn("serverProtocol"); QTest::addColumn("clientProtocol"); QTest::addColumn("works"); #if QT_DEPRECATED_SINCE(6, 3) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED QTest::addRow("DtlsV1_0 <-> DtlsV1_0") << QSsl::DtlsV1_0 << QSsl::DtlsV1_0 << true; QTest::addRow("DtlsV1_0OrLater <-> DtlsV1_0") << QSsl::DtlsV1_0OrLater << QSsl::DtlsV1_0 << true; QTest::addRow("DtlsV1_0 <-> DtlsV1_0OrLater") << QSsl::DtlsV1_0 << QSsl::DtlsV1_0OrLater << true; QTest::addRow("DtlsV1_0OrLater <-> DtlsV1_0OrLater") << QSsl::DtlsV1_0OrLater << QSsl::DtlsV1_0OrLater << true; QT_WARNING_POP #endif // QT_DEPRECATED_SINCE(6, 3) QTest::addRow("DtlsV1_2 <-> DtlsV1_2") << QSsl::DtlsV1_2 << QSsl::DtlsV1_2 << true; QTest::addRow("DtlsV1_2OrLater <-> DtlsV1_2") << QSsl::DtlsV1_2OrLater << QSsl::DtlsV1_2 << true; QTest::addRow("DtlsV1_2 <-> DtlsV1_2OrLater") << QSsl::DtlsV1_2 << QSsl::DtlsV1_2OrLater << true; QTest::addRow("DtlsV1_2OrLater <-> DtlsV1_2OrLater") << QSsl::DtlsV1_2OrLater << QSsl::DtlsV1_2OrLater << true; #if QT_DEPRECATED_SINCE(6, 3) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED QTest::addRow("DtlsV1_0 <-> DtlsV1_2") << QSsl::DtlsV1_0 << QSsl::DtlsV1_2 << false; QTest::addRow("DtlsV1_0 <-> DtlsV1_2OrLater") << QSsl::DtlsV1_0 << QSsl::DtlsV1_2OrLater << false; QTest::addRow("DtlsV1_2 <-> DtlsV1_0") << QSsl::DtlsV1_2 << QSsl::DtlsV1_0 << false; QTest::addRow("DtlsV1_2OrLater <-> DtlsV1_0") << QSsl::DtlsV1_2OrLater << QSsl::DtlsV1_0 << false; QT_WARNING_POP #endif // QT_DEPRECATED_SINCE(6, 3) } void tst_QDtls::protocolVersionMatching() { QFETCH(const QSsl::SslProtocol, serverProtocol); QFETCH(const QSsl::SslProtocol, clientProtocol); QFETCH(const bool, works); connectHandshakeReadingSlots(); connect(serverCrypto.data(), &QDtls::pskRequired, this, &tst_QDtls::pskRequested); connect(clientCrypto.data(), &QDtls::pskRequired, this, &tst_QDtls::pskRequested); auto serverConfig = defaultServerConfig; serverConfig.setProtocol(serverProtocol); QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); auto clientConfig = QSslConfiguration::defaultDtlsConfiguration(); clientConfig.setPeerVerifyMode(QSslSocket::VerifyNone); clientConfig.setProtocol(clientProtocol); QVERIFY(clientCrypto->setDtlsConfiguration(clientConfig)); QVERIFY(clientCrypto->setPeer(serverAddress, serverPort)); QVERIFY(clientCrypto->doHandshake(&clientSocket)); testLoop.enterLoopMSecs(handshakeTimeoutMS); if (works) { QDTLS_VERIFY_HANDSHAKE_SUCCESS(serverCrypto); QDTLS_VERIFY_HANDSHAKE_SUCCESS(clientCrypto); } else { QCOMPARE(serverCrypto->isConnectionEncrypted(), false); QVERIFY(serverCrypto->handshakeState() != QDtls::HandshakeComplete); QCOMPARE(clientCrypto->isConnectionEncrypted(), false); QVERIFY(clientCrypto->handshakeState() != QDtls::HandshakeComplete); } } void tst_QDtls::verificationErrors_data() { QTest::addColumn("abortHandshake"); QTest::addRow("abort-handshake") << true; QTest::addRow("ignore-errors") << false; } void tst_QDtls::verificationErrors() { connectHandshakeReadingSlots(); auto serverConfig = defaultServerConfig; serverConfig.setPrivateKey(serverKeySS); serverConfig.setLocalCertificate(selfSignedCert); QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); // And our client already has the default DTLS configuration. QVERIFY(clientCrypto->setPeer(serverAddress, serverPort)); // Now we are ready for handshake: QVERIFY(clientCrypto->doHandshake(&clientSocket)); testLoop.enterLoopMSecs(handshakeTimeoutMS); QVERIFY(!testLoop.timeout()); QDTLS_VERIFY_NO_ERROR(serverCrypto); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::PeerVerificationError); QCOMPARE(clientCrypto->handshakeState(), QDtls::PeerVerificationFailed); QVERIFY(!clientCrypto->isConnectionEncrypted()); QVERIFY(verificationErrorDetected(QSslError::HostNameMismatch)); QVERIFY(verificationErrorDetected(QSslError::SelfSignedCertificate)); const auto serverCert = clientCrypto->dtlsConfiguration().peerCertificate(); QVERIFY(!serverCert.isNull()); QCOMPARE(selfSignedCert, serverCert); QFETCH(const bool, abortHandshake); if (abortHandshake) { QVERIFY(!clientCrypto->abortHandshake(nullptr)); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::InvalidInputParameters); QVERIFY(clientCrypto->abortHandshake(&clientSocket)); QDTLS_VERIFY_NO_ERROR(clientCrypto); QVERIFY(!clientCrypto->isConnectionEncrypted()); QCOMPARE(clientCrypto->handshakeState(), QDtls::HandshakeNotStarted); QCOMPARE(clientCrypto->sessionCipher(), QSslCipher()); QCOMPARE(clientCrypto->sessionProtocol(), QSsl::UnknownProtocol); const auto config = clientCrypto->dtlsConfiguration(); QVERIFY(config.peerCertificate().isNull()); QCOMPARE(config.peerCertificateChain().size(), 0); QCOMPARE(clientCrypto->peerVerificationErrors().size(), 0); } else { clientCrypto->ignoreVerificationErrors(clientCrypto->peerVerificationErrors()); QVERIFY(!clientCrypto->resumeHandshake(nullptr)); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::InvalidInputParameters); QVERIFY(clientCrypto->resumeHandshake(&clientSocket)); QDTLS_VERIFY_HANDSHAKE_SUCCESS(clientCrypto); QVERIFY(clientCrypto->isConnectionEncrypted()); QCOMPARE(clientCrypto->handshakeState(), QDtls::HandshakeComplete); QCOMPARE(clientCrypto->peerVerificationErrors().size(), 0); } } void tst_QDtls::presetExpectedErrors_data() { QTest::addColumn>("expectedTlsErrors"); QTest::addColumn("works"); QList expectedErrors { { QSslError::HostNameMismatch, selfSignedCert } }; QTest::addRow("unexpected-self-signed") << expectedErrors << false; expectedErrors.push_back({QSslError::SelfSignedCertificate, selfSignedCert}); QTest::addRow("all-errors-ignored") << expectedErrors << true; } void tst_QDtls::presetExpectedErrors() { QFETCH(const QList, expectedTlsErrors); QFETCH(const bool, works); connectHandshakeReadingSlots(); auto serverConfig = defaultServerConfig; serverConfig.setPrivateKey(serverKeySS); serverConfig.setLocalCertificate(selfSignedCert); QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); clientCrypto->ignoreVerificationErrors(expectedTlsErrors); QVERIFY(clientCrypto->setPeer(serverAddress, serverPort)); QVERIFY(clientCrypto->doHandshake(&clientSocket)); testLoop.enterLoopMSecs(handshakeTimeoutMS); QVERIFY(!testLoop.timeout()); if (works) { QDTLS_VERIFY_HANDSHAKE_SUCCESS(serverCrypto); QCOMPARE(clientCrypto->handshakeState(), QDtls::HandshakeComplete); QVERIFY(clientCrypto->isConnectionEncrypted()); } else { QCOMPARE(clientCrypto->dtlsError(), QDtlsError::PeerVerificationError); QVERIFY(!clientCrypto->isConnectionEncrypted()); QCOMPARE(clientCrypto->handshakeState(), QDtls::PeerVerificationFailed); } } void tst_QDtls::verifyServerCertificate_data() { QTest::addColumn("verifyMode"); QTest::addColumn>("serverCerts"); QTest::addColumn("serverKey"); QTest::addColumn("peerName"); QTest::addColumn("encrypted"); { // A special case - null key (but with certificate): const auto chain = QSslCertificate::fromPath(certDirPath + QStringLiteral("bogus-server.crt")); QCOMPARE(chain.size(), 1); QSslKey nullKey; // Only one row - server must fail to start handshake immediately. QTest::newRow("valid-server-cert-no-key : VerifyPeer") << QSslSocket::VerifyPeer << chain << nullKey << QString() << false; } { // Valid certificate: auto chain = QSslCertificate::fromPath(certDirPath + QStringLiteral("bogus-server.crt")); QCOMPARE(chain.size(), 1); const auto caCert = QSslCertificate::fromPath(certDirPath + QStringLiteral("bogus-ca.crt")); QCOMPARE(caCert.size(), 1); chain += caCert; QFile keyFile(certDirPath + QStringLiteral("bogus-server.key")); QVERIFY(keyFile.open(QIODevice::ReadOnly)); const QSslKey key(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); QVERIFY(!key.isNull()); auto cert = chain.first(); const QString name(cert.subjectInfo(QSslCertificate::CommonName).first()); QTest::newRow("valid-server-cert : AutoVerifyPeer") << QSslSocket::AutoVerifyPeer << chain << key << name << true; QTest::newRow("valid-server-cert : QueryPeer") << QSslSocket::QueryPeer << chain << key << name << true; QTest::newRow("valid-server-cert : VerifyNone") << QSslSocket::VerifyNone << chain << key << name << true; QTest::newRow("valid-server-cert : VerifyPeer (add CA)") << QSslSocket::VerifyPeer << chain << key << name << true; QTest::newRow("valid-server-cert : VerifyPeer (no CA)") << QSslSocket::VerifyPeer << chain << key << name << false; QTest::newRow("valid-server-cert : VerifyPeer (name mismatch)") << QSslSocket::VerifyPeer << chain << key << QString() << false; } } void tst_QDtls::verifyServerCertificate() { QFETCH(const QSslSocket::PeerVerifyMode, verifyMode); QFETCH(const QList, serverCerts); QFETCH(const QSslKey, serverKey); QFETCH(const QString, peerName); QFETCH(const bool, encrypted); auto serverConfig = defaultServerConfig; serverConfig.setLocalCertificateChain(serverCerts); serverConfig.setPrivateKey(serverKey); QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); auto clientConfig = QSslConfiguration::defaultDtlsConfiguration(); if (serverCerts.size() == 2 && encrypted) { auto caCerts = clientConfig.caCertificates(); caCerts.append(serverCerts.at(1)); clientConfig.setCaCertificates(caCerts); } clientConfig.setPeerVerifyMode(verifyMode); QVERIFY(clientCrypto->setDtlsConfiguration(clientConfig)); QVERIFY(clientCrypto->setPeer(serverAddress, serverPort, peerName)); connectHandshakeReadingSlots(); QVERIFY(clientCrypto->doHandshake(&clientSocket)); testLoop.enterLoopMSecs(handshakeTimeoutMS); QVERIFY(!testLoop.timeout()); if (serverKey.isNull() && !serverCerts.isEmpty()) { QDTLS_VERIFY_NO_ERROR(clientCrypto); QCOMPARE(clientCrypto->handshakeState(), QDtls::HandshakeInProgress); QCOMPARE(serverCrypto->dtlsError(), QDtlsError::TlsInitializationError); QCOMPARE(serverCrypto->handshakeState(), QDtls::HandshakeNotStarted); return; } if (encrypted) { QDTLS_VERIFY_HANDSHAKE_SUCCESS(serverCrypto); QDTLS_VERIFY_HANDSHAKE_SUCCESS(clientCrypto); } else { QVERIFY(!clientCrypto->isConnectionEncrypted()); QCOMPARE(clientCrypto->handshakeState(), QDtls::PeerVerificationFailed); QVERIFY(clientCrypto->peerVerificationErrors().size()); QVERIFY(clientCrypto->writeDatagramEncrypted(&clientSocket, "something") < 0); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::InvalidOperation); } } void tst_QDtls::verifyClientCertificate_data() { QTest::addColumn("verifyMode"); QTest::addColumn>("clientCerts"); QTest::addColumn("clientKey"); QTest::addColumn("encrypted"); { // No certficates, no key: QList chain; QSslKey key; QTest::newRow("no-cert : AutoVerifyPeer") << QSslSocket::AutoVerifyPeer << chain << key << true; QTest::newRow("no-cert : QueryPeer") << QSslSocket::QueryPeer << chain << key << true; QTest::newRow("no-cert : VerifyNone") << QSslSocket::VerifyNone << chain << key << true; QTest::newRow("no-cert : VerifyPeer") << QSslSocket::VerifyPeer << chain << key << false; } { const auto chain = QSslCertificate::fromPath(certDirPath + QStringLiteral("fluke.cert")); QCOMPARE(chain.size(), 1); QFile keyFile(certDirPath + QStringLiteral("fluke.key")); QVERIFY(keyFile.open(QIODevice::ReadOnly)); const QSslKey key(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); QVERIFY(!key.isNull()); QTest::newRow("self-signed-cert : AutoVerifyPeer") << QSslSocket::AutoVerifyPeer << chain << key << true; QTest::newRow("self-signed-cert : QueryPeer") << QSslSocket::QueryPeer << chain << key << true; QTest::newRow("self-signed-cert : VerifyNone") << QSslSocket::VerifyNone << chain << key << true; QTest::newRow("self-signed-cert : VerifyPeer") << QSslSocket::VerifyPeer << chain << key << false; } { // Valid certificate, but wrong usage (server certificate): const auto chain = QSslCertificate::fromPath(certDirPath + QStringLiteral("bogus-server.crt")); QCOMPARE(chain.size(), 1); QFile keyFile(certDirPath + QStringLiteral("bogus-server.key")); QVERIFY(keyFile.open(QIODevice::ReadOnly)); const QSslKey key(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); QVERIFY(!key.isNull()); QTest::newRow("valid-server-cert : AutoVerifyPeer") << QSslSocket::AutoVerifyPeer << chain << key << true; QTest::newRow("valid-server-cert : QueryPeer") << QSslSocket::QueryPeer << chain << key << true; QTest::newRow("valid-server-cert : VerifyNone") << QSslSocket::VerifyNone << chain << key << true; QTest::newRow("valid-server-cert : VerifyPeer") << QSslSocket::VerifyPeer << chain << key << false; } { // Valid certificate, correct usage (client certificate): auto chain = QSslCertificate::fromPath(certDirPath + QStringLiteral("bogus-client.crt")); QCOMPARE(chain.size(), 1); QFile keyFile(certDirPath + QStringLiteral("bogus-client.key")); QVERIFY(keyFile.open(QIODevice::ReadOnly)); const QSslKey key(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); QVERIFY(!key.isNull()); QTest::newRow("valid-client-cert : AutoVerifyPeer") << QSslSocket::AutoVerifyPeer << chain << key << true; QTest::newRow("valid-client-cert : QueryPeer") << QSslSocket::QueryPeer << chain << key << true; QTest::newRow("valid-client-cert : VerifyNone") << QSslSocket::VerifyNone << chain << key << true; QTest::newRow("valid-client-cert : VerifyPeer") << QSslSocket::VerifyPeer << chain << key << true; // Valid certificate, correct usage (client certificate), with chain: chain += QSslCertificate::fromPath(certDirPath + QStringLiteral("bogus-ca.crt")); QCOMPARE(chain.size(), 2); QTest::newRow("valid-client-chain : AutoVerifyPeer") << QSslSocket::AutoVerifyPeer << chain << key << true; QTest::newRow("valid-client-chain : QueryPeer") << QSslSocket::QueryPeer << chain << key << true; QTest::newRow("valid-client-chain : VerifyNone") << QSslSocket::VerifyNone << chain << key << true; QTest::newRow("valid-client-chain : VerifyPeer") << QSslSocket::VerifyPeer << chain << key << true; } } void tst_QDtls::verifyClientCertificate() { connectHandshakeReadingSlots(); QFETCH(const QSslSocket::PeerVerifyMode, verifyMode); QFETCH(const QList, clientCerts); QFETCH(const QSslKey, clientKey); QFETCH(const bool, encrypted); QSslConfiguration serverConfig = defaultServerConfig; serverConfig.setLocalCertificate(selfSignedCert); serverConfig.setPrivateKey(serverKeySS); serverConfig.setPeerVerifyMode(verifyMode); if (verifyMode == QSslSocket::VerifyPeer && clientCerts.size()) { // Not always needed even if these conditions met, but does not hurt // either. const auto certs = QSslCertificate::fromPath(certDirPath + QStringLiteral("bogus-ca.crt")); QCOMPARE(certs.size(), 1); serverConfig.setCaCertificates(serverConfig.caCertificates() + certs); } QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); serverConfig = serverCrypto->dtlsConfiguration(); QVERIFY(serverConfig.peerCertificate().isNull()); QCOMPARE(serverConfig.peerCertificateChain().size(), 0); auto clientConfig = QSslConfiguration::defaultDtlsConfiguration(); clientConfig.setLocalCertificateChain(clientCerts); clientConfig.setPrivateKey(clientKey); clientConfig.setPeerVerifyMode(QSslSocket::VerifyNone); QVERIFY(clientCrypto->setDtlsConfiguration(clientConfig)); QVERIFY(clientCrypto->setPeer(serverAddress, serverPort)); QVERIFY(clientCrypto->doHandshake(&clientSocket)); QDTLS_VERIFY_NO_ERROR(clientCrypto); testLoop.enterLoopMSecs(handshakeTimeoutMS); serverConfig = serverCrypto->dtlsConfiguration(); if (verifyMode == QSslSocket::VerifyNone || clientCerts.isEmpty()) { QVERIFY(serverConfig.peerCertificate().isNull()); QCOMPARE(serverConfig.peerCertificateChain().size(), 0); } else { QCOMPARE(serverConfig.peerCertificate(), clientCerts.first()); QCOMPARE(serverConfig.peerCertificateChain(), clientCerts); } if (encrypted) { QDTLS_VERIFY_HANDSHAKE_SUCCESS(serverCrypto); QDTLS_VERIFY_HANDSHAKE_SUCCESS(clientCrypto); } else { QVERIFY(!serverCrypto->isConnectionEncrypted()); QCOMPARE(serverCrypto->handshakeState(), QDtls::PeerVerificationFailed); QVERIFY(serverCrypto->dtlsErrorString().size() > 0); QVERIFY(serverCrypto->peerVerificationErrors().size() > 0); QVERIFY(!clientCrypto->isConnectionEncrypted()); QDTLS_VERIFY_NO_ERROR(clientCrypto); QCOMPARE(clientCrypto->handshakeState(), QDtls::HandshakeInProgress); } } void tst_QDtls::blacklistedCerificate() { const auto serverChain = QSslCertificate::fromPath(certDirPath + QStringLiteral("fake-login.live.com.pem")); QCOMPARE(serverChain.size(), 1); QFile keyFile(certDirPath + QStringLiteral("fake-login.live.com.key")); QVERIFY(keyFile.open(QIODevice::ReadOnly)); const QSslKey key(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); QVERIFY(!key.isNull()); auto serverConfig = defaultServerConfig; serverConfig.setLocalCertificateChain(serverChain); serverConfig.setPrivateKey(key); QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); connectHandshakeReadingSlots(); const QString name(serverChain.first().subjectInfo(QSslCertificate::CommonName).first()); QVERIFY(clientCrypto->setPeer(serverAddress, serverPort, name)); QVERIFY(clientCrypto->doHandshake(&clientSocket)); testLoop.enterLoopMSecs(handshakeTimeoutMS); QVERIFY(!testLoop.timeout()); QCOMPARE(clientCrypto->handshakeState(), QDtls::PeerVerificationFailed); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::PeerVerificationError); QVERIFY(!clientCrypto->isConnectionEncrypted()); QVERIFY(verificationErrorDetected(QSslError::CertificateBlacklisted)); } void tst_QDtls::readWriteEncrypted_data() { QTest::addColumn("serverSideShutdown"); QTest::addRow("client-shutdown") << false; QTest::addRow("server-shutdown") << true; } void tst_QDtls::readWriteEncrypted() { connectHandshakeReadingSlots(); auto serverConfig = defaultServerConfig; serverConfig.setLocalCertificate(selfSignedCert); serverConfig.setPrivateKey(serverKeySS); QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); auto clientConfig = QSslConfiguration::defaultDtlsConfiguration(); clientConfig.setCaCertificates({selfSignedCert}); QVERIFY(clientCrypto->setDtlsConfiguration(clientConfig)); QVERIFY(clientCrypto->setPeer(serverAddress, serverPort, hostName)); // 0. Verify we cannot write any encrypted message without handshake done QDTLS_VERIFY_NO_ERROR(clientCrypto); QVERIFY(clientCrypto->writeDatagramEncrypted(&clientSocket, serverExpectedPlainText) <= 0); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::InvalidOperation); QVERIFY(!clientCrypto->shutdown(&clientSocket)); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::InvalidOperation); QDTLS_VERIFY_NO_ERROR(serverCrypto); QVERIFY(serverCrypto->writeDatagramEncrypted(&serverSocket, clientExpectedPlainText) <= 0); QCOMPARE(serverCrypto->dtlsError(), QDtlsError::InvalidOperation); QVERIFY(!serverCrypto->shutdown(&serverSocket)); QCOMPARE(serverCrypto->dtlsError(), QDtlsError::InvalidOperation); // 1. Initiate a handshake: QVERIFY(clientCrypto->doHandshake(&clientSocket)); QDTLS_VERIFY_NO_ERROR(clientCrypto); // 1.1 Verify we cannot read yet. What the datagram is - not really important, // invalid state/operation - is what we verify: const QByteArray dummy = clientCrypto->decryptDatagram(&clientSocket, "BS dgram"); QCOMPARE(dummy.size(), 0); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::InvalidOperation); // 1.2 Finish the handshake: testLoop.enterLoopMSecs(handshakeTimeoutMS); QVERIFY(!testLoop.timeout()); QDTLS_VERIFY_HANDSHAKE_SUCCESS(clientCrypto); QDTLS_VERIFY_HANDSHAKE_SUCCESS(serverCrypto); // 2. Change reading slots: connectEncryptedReadingSlots(); // 3. Test parameter validation: QVERIFY(clientCrypto->writeDatagramEncrypted(nullptr, serverExpectedPlainText) <= 0); QCOMPARE(clientCrypto->dtlsError(), QDtlsError::InvalidInputParameters); // 4. Write the client's message: qint64 clientBytesWritten = clientCrypto->writeDatagramEncrypted(&clientSocket, serverExpectedPlainText); QDTLS_VERIFY_NO_ERROR(clientCrypto); QVERIFY(clientBytesWritten > 0); // 5. Exchange client/server messages: testLoop.enterLoopMSecs(dataExchangeTimeoutMS); QVERIFY(!testLoop.timeout()); QCOMPARE(serverExpectedPlainText, serverReceivedPlainText); QCOMPARE(clientExpectedPlainText, clientReceivedPlainText); QFETCH(const bool, serverSideShutdown); DtlsPtr &crypto = serverSideShutdown ? serverCrypto : clientCrypto; QUdpSocket *socket = serverSideShutdown ? &serverSocket : &clientSocket; // 6. Parameter validation: QVERIFY(!crypto->shutdown(nullptr)); QCOMPARE(crypto->dtlsError(), QDtlsError::InvalidInputParameters); // 7. Send shutdown alert: QVERIFY(crypto->shutdown(socket)); QDTLS_VERIFY_NO_ERROR(crypto); QCOMPARE(crypto->handshakeState(), QDtls::HandshakeNotStarted); QVERIFY(!crypto->isConnectionEncrypted()); // 8. Receive this read notification and handle it: testLoop.enterLoopMSecs(dataExchangeTimeoutMS); QVERIFY(!testLoop.timeout()); DtlsPtr &peerCrypto = serverSideShutdown ? clientCrypto : serverCrypto; QVERIFY(!peerCrypto->isConnectionEncrypted()); QCOMPARE(peerCrypto->handshakeState(), QDtls::HandshakeNotStarted); QCOMPARE(peerCrypto->dtlsError(), QDtlsError::RemoteClosedConnectionError); } void tst_QDtls::datagramFragmentation() { connectHandshakeReadingSlots(); auto serverConfig = defaultServerConfig; serverConfig.setLocalCertificate(selfSignedCert); serverConfig.setPrivateKey(serverKeySS); QVERIFY(serverCrypto->setDtlsConfiguration(serverConfig)); auto clientConfig = QSslConfiguration::defaultDtlsConfiguration(); clientConfig.setPeerVerifyMode(QSslSocket::VerifyNone); QVERIFY(clientCrypto->setDtlsConfiguration(clientConfig)); QVERIFY(clientCrypto->setPeer(serverAddress, serverPort)); QVERIFY(clientCrypto->doHandshake(&clientSocket)); testLoop.enterLoopMSecs(handshakeTimeoutMS); QVERIFY(!testLoop.timeout()); QDTLS_VERIFY_HANDSHAKE_SUCCESS(clientCrypto); QDTLS_VERIFY_HANDSHAKE_SUCCESS(serverCrypto); // Done with handshake, reconnect readyRead: connectEncryptedReadingSlots(); // Verify our dgram is not fragmented and some error set (either UnderlyingSocketError // if OpenSSL somehow had attempted a write or TlsFatalError in case OpenSSL // noticed how big the chunk is). QVERIFY(clientCrypto->writeDatagramEncrypted(&clientSocket, QByteArray(1024 * 17, Qt::Uninitialized)) <= 0); QVERIFY(clientCrypto->dtlsError() != QDtlsError::NoError); // Error to write does not mean QDtls is broken: QVERIFY(clientCrypto->isConnectionEncrypted()); QVERIFY(clientCrypto->writeDatagramEncrypted(&clientSocket, "Hello, I'm a tiny datagram") > 0); QDTLS_VERIFY_NO_ERROR(clientCrypto); } void tst_QDtls::handshakeReadyRead() { QUdpSocket *socket = qobject_cast(sender()); Q_ASSERT(socket); if (socket->pendingDatagramSize() <= 0) return; const bool isServer = socket == &serverSocket; DtlsPtr &crypto = isServer ? serverCrypto : clientCrypto; DtlsPtr &peerCrypto = isServer ? clientCrypto : serverCrypto; QHostAddress addr; quint16 port = 0; QByteArray dgram(socket->pendingDatagramSize(), Qt::Uninitialized); const qint64 size = socket->readDatagram(dgram.data(), dgram.size(), &addr, &port); if (size != dgram.size()) return; if (isServer) { if (!clientPort) { // It's probably an initial 'ClientHello' message. Let's set remote's // address/port. But first we make sure it is, indeed, 'ClientHello'. if (int(dgram.constData()[0]) != 22) return; if (addr.isNull() || addr.isBroadcast()) // Could never be us (client), bail out return; if (!crypto->setPeer(addr, port)) return testLoop.exitLoop(); // Check parameter validation: if (crypto->doHandshake(nullptr, dgram) || crypto->dtlsError() != QDtlsError::InvalidInputParameters) return testLoop.exitLoop(); if (crypto->doHandshake(&serverSocket, {}) || crypto->dtlsError() != QDtlsError::InvalidInputParameters) return testLoop.exitLoop(); // Make sure we cannot decrypt yet: const QByteArray dummyDgram = crypto->decryptDatagram(&serverSocket, dgram); if (dummyDgram.size() > 0 || crypto->dtlsError() != QDtlsError::InvalidOperation) return testLoop.exitLoop(); clientAddress = addr; clientPort = port; } else if (clientPort != port || clientAddress != addr) { return; } if (serverDropDgram) { serverDropDgram = false; return; } } else if (clientDropDgram) { clientDropDgram = false; return; } if (!crypto->doHandshake(socket, dgram)) return testLoop.exitLoop(); const auto state = crypto->handshakeState(); if (state != QDtls::HandshakeInProgress && state != QDtls::HandshakeComplete) return testLoop.exitLoop(); if (state == QDtls::HandshakeComplete && peerCrypto->handshakeState() == QDtls::HandshakeComplete) testLoop.exitLoop(); } void tst_QDtls::encryptedReadyRead() { QUdpSocket *socket = qobject_cast(sender()); Q_ASSERT(socket); if (socket->pendingDatagramSize() <= 0) return; QByteArray dtlsMessage(int(socket->pendingDatagramSize()), Qt::Uninitialized); QHostAddress addr; quint16 port = 0; const qint64 bytesRead = socket->readDatagram(dtlsMessage.data(), dtlsMessage.size(), &addr, &port); if (bytesRead <= 0) return; dtlsMessage.resize(int(bytesRead)); if (socket == &serverSocket) { if (addr != clientAddress || port != clientPort) return; if (serverExpectedPlainText == dtlsMessage) // No way it can happen! return testLoop.exitLoop(); serverReceivedPlainText = serverCrypto->decryptDatagram(nullptr, dtlsMessage); if (serverReceivedPlainText.size() > 0 || serverCrypto->dtlsError() != QDtlsError::InvalidInputParameters) return testLoop.exitLoop(); serverReceivedPlainText = serverCrypto->decryptDatagram(&serverSocket, dtlsMessage); const int messageType = dtlsMessage.data()[0]; if (serverReceivedPlainText != serverExpectedPlainText && (messageType == 23 || messageType == 21)) { // Type 23 is for application data, 21 is shutdown alert. Here we test // write/read operations and shutdown alerts, not expecting and thus // ignoring any other types of messages. return testLoop.exitLoop(); } if (serverCrypto->dtlsError() != QDtlsError::NoError) return testLoop.exitLoop(); // Verify it cannot be done twice: const QByteArray replayed = serverCrypto->decryptDatagram(&serverSocket, dtlsMessage); if (replayed.size() > 0) return testLoop.exitLoop(); if (serverCrypto->writeDatagramEncrypted(&serverSocket, clientExpectedPlainText) <= 0) testLoop.exitLoop(); } else { if (port != serverPort) return; if (clientExpectedPlainText == dtlsMessage) // What a disaster! return testLoop.exitLoop(); clientReceivedPlainText = clientCrypto->decryptDatagram(&clientSocket, dtlsMessage); testLoop.exitLoop(); } } void tst_QDtls::pskRequested(QSslPreSharedKeyAuthenticator *auth) { Q_ASSERT(auth); auth->setPreSharedKey(presharedKey); } void tst_QDtls::handleHandshakeTimeout() { auto crypto = qobject_cast(sender()); Q_ASSERT(crypto); if (!crypto->handleTimeout(&clientSocket)) testLoop.exitLoop(); } void tst_QDtls::clientServerData() { QTest::addColumn("mode"); QTest::addRow("client") << QSslSocket::SslClientMode; QTest::addRow("server") << QSslSocket::SslServerMode; } void tst_QDtls::connectHandshakeReadingSlots() { connect(&serverSocket, &QUdpSocket::readyRead, this, &tst_QDtls::handshakeReadyRead); connect(&clientSocket, &QUdpSocket::readyRead, this, &tst_QDtls::handshakeReadyRead); } void tst_QDtls::connectEncryptedReadingSlots() { serverSocket.disconnect(); clientSocket.disconnect(); connect(&serverSocket, &QUdpSocket::readyRead, this, &tst_QDtls::encryptedReadyRead); connect(&clientSocket, &QUdpSocket::readyRead, this, &tst_QDtls::encryptedReadyRead); } bool tst_QDtls::verificationErrorDetected(QSslError::SslError code) const { Q_ASSERT(clientCrypto.data()); const auto errors = clientCrypto->peerVerificationErrors(); for (const QSslError &error : errors) { if (error.error() == code) return true; } return false; } QHostAddress tst_QDtls::toNonAny(const QHostAddress &addr) { if (addr == QHostAddress::Any || addr == QHostAddress::AnyIPv4) return QHostAddress::LocalHost; if (addr == QHostAddress::AnyIPv6) return QHostAddress::LocalHostIPv6; return addr; } QT_END_NAMESPACE QTEST_MAIN(tst_QDtls) #include "tst_qdtls.moc"