diff options
Diffstat (limited to 'tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp')
-rw-r--r-- | tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp | 664 |
1 files changed, 584 insertions, 80 deletions
diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp index a355cfeb17..f4d3555531 100644 --- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp +++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp @@ -1,7 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2014 Governikus GmbH & Co. KG. +** Contact: http://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** @@ -10,9 +11,9 @@ ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser @@ -23,15 +24,15 @@ ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ - +#include <QtCore/qglobal.h> #include <QtCore/qthread.h> #include <QtNetwork/qhostaddress.h> #include <QtNetwork/qhostinfo.h> @@ -41,27 +42,42 @@ #include <QtNetwork/qsslkey.h> #include <QtNetwork/qsslsocket.h> #include <QtNetwork/qtcpserver.h> +#include <QtNetwork/qsslpresharedkeyauthenticator.h> #include <QtTest/QtTest> #include <QNetworkProxy> #include <QAuthenticator> #include "private/qhostinfo_p.h" +#include "private/qiodevice_p.h" // for QIODEVICE_BUFFERSIZE + +#include "../../../network-settings.h" + #ifndef QT_NO_SSL +#ifndef QT_NO_OPENSSL #include "private/qsslsocket_openssl_p.h" #include "private/qsslsocket_openssl_symbols_p.h" -#include "private/qsslconfiguration_p.h" #endif +#include "private/qsslsocket_p.h" +#include "private/qsslconfiguration_p.h" -#include "../../../network-settings.h" - -#ifndef QT_NO_SSL Q_DECLARE_METATYPE(QSslSocket::SslMode) typedef QList<QSslError::SslError> SslErrorList; Q_DECLARE_METATYPE(SslErrorList) Q_DECLARE_METATYPE(QSslError) +Q_DECLARE_METATYPE(QSslKey) Q_DECLARE_METATYPE(QSsl::SslProtocol) +Q_DECLARE_METATYPE(QSslSocket::PeerVerifyMode); +typedef QSharedPointer<QSslSocket> QSslSocketPtr; + +// Non-OpenSSL backends are not able to report a specific error code +// for self-signed certificate for certificates. +#ifndef QT_NO_OPENSSL +#define FLUKE_CERTIFICATE_ERROR QSslError::SelfSignedCertificate +#else +#define FLUKE_CERTIFICATE_ERROR QSslError::CertificateUntrusted #endif +#endif // QT_NO_SSL #if defined Q_OS_HPUX && defined Q_CC_GNU // This error is delivered every time we try to use the fluke CA @@ -69,9 +85,14 @@ Q_DECLARE_METATYPE(QSsl::SslProtocol) #define QSSLSOCKET_CERTUNTRUSTED_WORKAROUND #endif -#ifndef QT_NO_SSL -typedef QSharedPointer<QSslSocket> QSslSocketPtr; -#endif +// Use this cipher to force PSK key sharing. +// Also, it's a cipher w/o auth, to check that we emit the signals warning +// about the identity of the peer. +static const QString PSK_CIPHER_WITHOUT_AUTH = QStringLiteral("PSK-AES256-CBC-SHA"); +static const quint16 PSK_SERVER_PORT = 4433; +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 tst_QSslSocket : public QObject { @@ -96,6 +117,19 @@ public: #ifndef QT_NO_SSL QSslSocketPtr newSocket(); + +#ifndef QT_NO_OPENSSL + enum PskConnectTestType { + PskConnectDoNotHandlePsk, + PskConnectEmptyCredentials, + PskConnectWrongCredentials, + PskConnectWrongIdentity, + PskConnectWrongPreSharedKey, + PskConnectRightCredentialsPeerVerifyFailure, + PskConnectRightCredentialsVerifyPeer, + PskConnectRightCredentialsDoNotVerifyPeer, + }; +#endif #endif public slots: @@ -168,7 +202,6 @@ private slots: void waitForMinusOne(); void verifyMode(); void verifyDepth(); - void peerVerifyError(); void disconnectFromHostWhenConnecting(); void disconnectFromHostWhenConnected(); void resetProxy(); @@ -190,8 +223,15 @@ private slots: void qtbug18498_peek2(); void dhServer(); void ecdhServer(); + void verifyClientCertificate_data(); + void verifyClientCertificate(); void setEmptyDefaultConfiguration(); // this test should be last +#ifndef QT_NO_OPENSSL + void simplePskConnect_data(); + void simplePskConnect(); +#endif + static void exitLoop() { // Safe exit - if we aren't in an event loop, don't @@ -224,6 +264,12 @@ private: static int loopLevel; }; +#ifndef QT_NO_SSL +#ifndef QT_NO_OPENSSL +Q_DECLARE_METATYPE(tst_QSslSocket::PskConnectTestType) +#endif +#endif + int tst_QSslSocket::loopLevel = 0; tst_QSslSocket::tst_QSslSocket() @@ -233,6 +279,11 @@ tst_QSslSocket::tst_QSslSocket() qRegisterMetaType<QSslError>("QSslError"); qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState"); qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError"); + +#ifndef QT_NO_OPENSSL + qRegisterMetaType<QSslPreSharedKeyAuthenticator *>(); + qRegisterMetaType<tst_QSslSocket::PskConnectTestType>(); +#endif #endif } @@ -546,37 +597,53 @@ void tst_QSslSocket::sslErrors_data() { QTest::addColumn<QString>("host"); QTest::addColumn<int>("port"); - QTest::addColumn<SslErrorList>("expected"); - QTest::newRow(qPrintable(QtNetworkSettings::serverLocalName())) - << QtNetworkSettings::serverLocalName() - << 993 - << (SslErrorList() << QSslError::HostNameMismatch - << QSslError::SelfSignedCertificate); + QString name = QtNetworkSettings::serverLocalName(); + QTest::newRow(qPrintable(name)) << name << 993; + + name = QHostInfo::fromName(QtNetworkSettings::serverName()).addresses().first().toString(); + QTest::newRow(qPrintable(name)) << name << 443; } void tst_QSslSocket::sslErrors() { QFETCH(QString, host); QFETCH(int, port); - QFETCH(SslErrorList, expected); QSslSocketPtr socket = newSocket(); + QSignalSpy sslErrorsSpy(socket.data(), SIGNAL(sslErrors(QList<QSslError>))); + QSignalSpy peerVerifyErrorSpy(socket.data(), SIGNAL(peerVerifyError(QSslError))); + socket->connectToHostEncrypted(host, port); if (!socket->waitForConnected()) - QEXPECT_FAIL("imap.trolltech.com", "server not open to internet", Continue); - socket->waitForEncrypted(5000); + QSKIP("Skipping flaky test - See QTBUG-29941"); + socket->waitForEncrypted(10000); - SslErrorList output; - foreach (QSslError error, socket->sslErrors()) { - output << error.error(); - } + // check the SSL errors contain HostNameMismatch and an error due to + // the certificate being self-signed + SslErrorList sslErrors; + foreach (const QSslError &err, socket->sslErrors()) + sslErrors << err.error(); + qSort(sslErrors); + QVERIFY(sslErrors.contains(QSslError::HostNameMismatch)); + QVERIFY(sslErrors.contains(FLUKE_CERTIFICATE_ERROR)); -#ifdef QSSLSOCKET_CERTUNTRUSTED_WORKAROUND - if (output.count() && output.last() == QSslError::CertificateUntrusted) - output.takeLast(); -#endif - QCOMPARE(output, expected); + // check the same errors were emitted by sslErrors + QVERIFY(!sslErrorsSpy.isEmpty()); + SslErrorList emittedErrors; + foreach (const QSslError &err, qvariant_cast<QList<QSslError> >(sslErrorsSpy.first().first())) + emittedErrors << err.error(); + qSort(emittedErrors); + QCOMPARE(sslErrors, emittedErrors); + + // check the same errors were emitted by peerVerifyError + QVERIFY(!peerVerifyErrorSpy.isEmpty()); + SslErrorList peerErrors; + const QList<QVariantList> &peerVerifyList = peerVerifyErrorSpy; + foreach (const QVariantList &args, peerVerifyList) + peerErrors << qvariant_cast<QSslError>(args.first()).error(); + qSort(peerErrors); + QCOMPARE(sslErrors, peerErrors); } void tst_QSslSocket::addCaCertificate() @@ -929,7 +996,7 @@ void tst_QSslSocket::protocol() socket->abort(); } #endif -#ifndef OPENSSL_NO_SSL2 +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) { // qt-test-server allows SSLV2. socket->setProtocol(QSsl::SslV2); @@ -996,12 +1063,17 @@ public: const QString &certFile = SRCDIR "certs/fluke.cert", const QString &interFile = QString()) : socket(0), + ignoreSslErrors(true), + peerVerifyMode(QSslSocket::AutoVerifyPeer), protocol(QSsl::TlsV1_0), m_keyFile(keyFile), m_certFile(certFile), m_interFile(interFile) { } QSslSocket *socket; + QString addCaCertificates; + bool ignoreSslErrors; + QSslSocket::PeerVerifyMode peerVerifyMode; QSsl::SslProtocol protocol; QString m_keyFile; QString m_certFile; @@ -1012,8 +1084,10 @@ protected: void incomingConnection(qintptr socketDescriptor) { socket = new QSslSocket(this); + socket->setPeerVerifyMode(peerVerifyMode); socket->setProtocol(protocol); - connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); + if (ignoreSslErrors) + connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); QFile file(m_keyFile); QVERIFY(file.open(QIODevice::ReadOnly)); @@ -1021,6 +1095,14 @@ protected: QVERIFY(!key.isNull()); socket->setPrivateKey(key); + // Add CA certificates to verify client certificate + if (!addCaCertificates.isEmpty()) { + QList<QSslCertificate> caCert = QSslCertificate::fromPath(addCaCertificates); + QVERIFY(!caCert.isEmpty()); + QVERIFY(!caCert.first().isNull()); + socket->addCaCertificates(caCert); + } + // If we have a cert issued directly from the CA if (m_interFile.isEmpty()) { QList<QSslCertificate> localCert = QSslCertificate::fromPath(m_certFile); @@ -1066,7 +1148,7 @@ void tst_QSslSocket::protocolServerSide_data() QTest::addColumn<QSsl::SslProtocol>("clientProtocol"); QTest::addColumn<bool>("works"); -#ifndef OPENSSL_NO_SSL2 +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("ssl2-ssl2") << QSsl::SslV2 << QSsl::SslV2 << false; // no idea why it does not work, but we don't care about SSL 2 #endif QTest::newRow("ssl3-ssl3") << QSsl::SslV3 << QSsl::SslV3 << true; @@ -1075,7 +1157,7 @@ void tst_QSslSocket::protocolServerSide_data() QTest::newRow("any-any") << QSsl::AnyProtocol << QSsl::AnyProtocol << true; QTest::newRow("secure-secure") << QSsl::SecureProtocols << QSsl::SecureProtocols << true; -#ifndef OPENSSL_NO_SSL2 +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("ssl2-ssl3") << QSsl::SslV2 << QSsl::SslV3 << false; QTest::newRow("ssl2-tls1.0") << QSsl::SslV2 << QSsl::TlsV1_0 << false; QTest::newRow("ssl2-tls1ssl3") << QSsl::SslV2 << QSsl::TlsV1SslV3 << false; @@ -1083,33 +1165,33 @@ void tst_QSslSocket::protocolServerSide_data() QTest::newRow("ssl2-any") << QSsl::SslV2 << QSsl::AnyProtocol << false; // no idea why it does not work, but we don't care about SSL 2 #endif -#ifndef OPENSSL_NO_SSL2 +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("ssl3-ssl2") << QSsl::SslV3 << QSsl::SslV2 << false; #endif QTest::newRow("ssl3-tls1.0") << QSsl::SslV3 << QSsl::TlsV1_0 << false; QTest::newRow("ssl3-tls1ssl3") << QSsl::SslV3 << QSsl::TlsV1SslV3 << true; QTest::newRow("ssl3-secure") << QSsl::SslV3 << QSsl::SecureProtocols << false; -#ifndef OPENSSL_NO_SSL2 +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("ssl3-any") << QSsl::SslV3 << QSsl::AnyProtocol << false; // we won't set a SNI header here because we connect to a // numerical IP, so OpenSSL will send a SSL 2 handshake #else QTest::newRow("ssl3-any") << QSsl::SslV3 << QSsl::AnyProtocol << true; #endif -#ifndef OPENSSL_NO_SSL2 +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("tls1.0-ssl2") << QSsl::TlsV1_0 << QSsl::SslV2 << false; #endif QTest::newRow("tls1.0-ssl3") << QSsl::TlsV1_0 << QSsl::SslV3 << false; QTest::newRow("tls1-tls1ssl3") << QSsl::TlsV1_0 << QSsl::TlsV1SslV3 << true; QTest::newRow("tls1.0-secure") << QSsl::TlsV1_0 << QSsl::SecureProtocols << true; -#ifndef OPENSSL_NO_SSL2 +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("tls1.0-any") << QSsl::TlsV1_0 << QSsl::AnyProtocol << false; // we won't set a SNI header here because we connect to a // numerical IP, so OpenSSL will send a SSL 2 handshake #else QTest::newRow("tls1.0-any") << QSsl::TlsV1_0 << QSsl::AnyProtocol << true; #endif -#ifndef OPENSSL_NO_SSL2 +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("tls1ssl3-ssl2") << QSsl::TlsV1SslV3 << QSsl::SslV2 << false; #endif QTest::newRow("tls1ssl3-ssl3") << QSsl::TlsV1SslV3 << QSsl::SslV3 << true; @@ -1117,7 +1199,7 @@ void tst_QSslSocket::protocolServerSide_data() QTest::newRow("tls1ssl3-secure") << QSsl::TlsV1SslV3 << QSsl::SecureProtocols << true; QTest::newRow("tls1ssl3-any") << QSsl::TlsV1SslV3 << QSsl::AnyProtocol << true; -#ifndef OPENSSL_NO_SSL2 +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("secure-ssl2") << QSsl::SecureProtocols << QSsl::SslV2 << false; #endif QTest::newRow("secure-ssl3") << QSsl::SecureProtocols << QSsl::SslV3 << false; @@ -1125,7 +1207,7 @@ void tst_QSslSocket::protocolServerSide_data() QTest::newRow("secure-tls1ssl3") << QSsl::SecureProtocols << QSsl::TlsV1SslV3 << true; QTest::newRow("secure-any") << QSsl::SecureProtocols << QSsl::AnyProtocol << true; -#ifndef OPENSSL_NO_SSL2 +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("any-ssl2") << QSsl::AnyProtocol << QSsl::SslV2 << false; // no idea why it does not work, but we don't care about SSL 2 #endif QTest::newRow("any-ssl3") << QSsl::AnyProtocol << QSsl::SslV3 << true; @@ -1950,7 +2032,7 @@ void tst_QSslSocket::verifyMode() QSKIP("Skipping flaky test - See QTBUG-29941"); QList<QSslError> expectedErrors = QList<QSslError>() - << QSslError(QSslError::SelfSignedCertificate, socket.peerCertificate()); + << QSslError(FLUKE_CERTIFICATE_ERROR, socket.peerCertificate()); QCOMPARE(socket.sslErrors(), expectedErrors); socket.abort(); @@ -1981,34 +2063,6 @@ void tst_QSslSocket::verifyDepth() QCOMPARE(socket.peerVerifyDepth(), 1); } -void tst_QSslSocket::peerVerifyError() -{ - QSslSocketPtr socket = newSocket(); - QSignalSpy sslErrorsSpy(socket.data(), SIGNAL(sslErrors(QList<QSslError>))); - QSignalSpy peerVerifyErrorSpy(socket.data(), SIGNAL(peerVerifyError(QSslError))); - - socket->connectToHostEncrypted(QHostInfo::fromName(QtNetworkSettings::serverName()).addresses().first().toString(), 443); - if (socket->waitForEncrypted(10000)) - QSKIP("Skipping flaky test - See QTBUG-29941"); - - // check HostNameMismatch was emitted by peerVerifyError - QVERIFY(!peerVerifyErrorSpy.isEmpty()); - SslErrorList peerErrors; - const QList<QVariantList> &peerVerifyList = peerVerifyErrorSpy; - foreach (const QVariantList &args, peerVerifyList) - peerErrors << qvariant_cast<QSslError>(args.first()).error(); - QVERIFY(peerErrors.contains(QSslError::HostNameMismatch)); - - // check HostNameMismatch was emitted by sslErrors - QVERIFY(!sslErrorsSpy.isEmpty()); - SslErrorList sslErrors; - foreach (const QSslError &err, qvariant_cast<QList<QSslError> >(sslErrorsSpy.first().first())) - sslErrors << err.error(); - QVERIFY(peerErrors.contains(QSslError::HostNameMismatch)); - - QCOMPARE(sslErrors.size(), peerErrors.size()); -} - void tst_QSslSocket::disconnectFromHostWhenConnecting() { QSslSocketPtr socket = newSocket(); @@ -2105,8 +2159,8 @@ void tst_QSslSocket::ignoreSslErrorsList_data() QList<QSslError> expectedSslErrors; // fromPath gives us a list of certs, but it actually only contains one QList<QSslCertificate> certs = QSslCertificate::fromPath(QLatin1String(SRCDIR "certs/qt-test-server-cacert.pem")); - QSslError rightError(QSslError::SelfSignedCertificate, certs.at(0)); - QSslError wrongError(QSslError::SelfSignedCertificate); + QSslError rightError(FLUKE_CERTIFICATE_ERROR, certs.at(0)); + QSslError wrongError(FLUKE_CERTIFICATE_ERROR); QTest::newRow("SSL-failure-empty-list") << expectedSslErrors << 1; @@ -2368,8 +2422,8 @@ void tst_QSslSocket::resume_data() QTest::newRow("ignoreAllErrors") << true << QList<QSslError>() << true; QList<QSslCertificate> certs = QSslCertificate::fromPath(QLatin1String(SRCDIR "certs/qt-test-server-cacert.pem")); - QSslError rightError(QSslError::SelfSignedCertificate, certs.at(0)); - QSslError wrongError(QSslError::SelfSignedCertificate); + QSslError rightError(FLUKE_CERTIFICATE_ERROR, certs.at(0)); + QSslError wrongError(FLUKE_CERTIFICATE_ERROR); errorsList.append(wrongError); QTest::newRow("ignoreSpecificErrors-Wrong") << true << errorsList << false; errorsList.clear(); @@ -2747,6 +2801,140 @@ void tst_QSslSocket::ecdhServer() QVERIFY(client->state() == QAbstractSocket::ConnectedState); } +void tst_QSslSocket::verifyClientCertificate_data() +{ + QTest::addColumn<QSslSocket::PeerVerifyMode>("peerVerifyMode"); + QTest::addColumn<QList<QSslCertificate> >("clientCerts"); + QTest::addColumn<QSslKey>("clientKey"); + QTest::addColumn<bool>("works"); + + // no certificate + QList<QSslCertificate> noCerts; + QSslKey noKey; + + QTest::newRow("NoCert:AutoVerifyPeer") << QSslSocket::AutoVerifyPeer << noCerts << noKey << true; + QTest::newRow("NoCert:QueryPeer") << QSslSocket::QueryPeer << noCerts << noKey << true; + QTest::newRow("NoCert:VerifyNone") << QSslSocket::VerifyNone << noCerts << noKey << true; + QTest::newRow("NoCert:VerifyPeer") << QSslSocket::VerifyPeer << noCerts << noKey << false; + + // self-signed certificate + QList<QSslCertificate> flukeCerts = QSslCertificate::fromPath(SRCDIR "certs/fluke.cert"); + QCOMPARE(flukeCerts.size(), 1); + + QFile flukeFile(SRCDIR "certs/fluke.key"); + QVERIFY(flukeFile.open(QIODevice::ReadOnly)); + QSslKey flukeKey(flukeFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + QVERIFY(!flukeKey.isNull()); + + QTest::newRow("SelfSignedCert:AutoVerifyPeer") << QSslSocket::AutoVerifyPeer << flukeCerts << flukeKey << true; + QTest::newRow("SelfSignedCert:QueryPeer") << QSslSocket::QueryPeer << flukeCerts << flukeKey << true; + QTest::newRow("SelfSignedCert:VerifyNone") << QSslSocket::VerifyNone << flukeCerts << flukeKey << true; + QTest::newRow("SelfSignedCert:VerifyPeer") << QSslSocket::VerifyPeer << flukeCerts << flukeKey << false; + + // valid certificate, but wrong usage (server certificate) + QList<QSslCertificate> serverCerts = QSslCertificate::fromPath(SRCDIR "certs/bogus-server.crt"); + QCOMPARE(serverCerts.size(), 1); + + QFile serverFile(SRCDIR "certs/bogus-server.key"); + QVERIFY(serverFile.open(QIODevice::ReadOnly)); + QSslKey serverKey(serverFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + QVERIFY(!serverKey.isNull()); + + QTest::newRow("ValidServerCert:AutoVerifyPeer") << QSslSocket::AutoVerifyPeer << serverCerts << serverKey << true; + QTest::newRow("ValidServerCert:QueryPeer") << QSslSocket::QueryPeer << serverCerts << serverKey << true; + QTest::newRow("ValidServerCert:VerifyNone") << QSslSocket::VerifyNone << serverCerts << serverKey << true; + QTest::newRow("ValidServerCert:VerifyPeer") << QSslSocket::VerifyPeer << serverCerts << serverKey << false; + + // valid certificate, correct usage (client certificate) + QList<QSslCertificate> validCerts = QSslCertificate::fromPath(SRCDIR "certs/bogus-client.crt"); + QCOMPARE(validCerts.size(), 1); + + QFile validFile(SRCDIR "certs/bogus-client.key"); + QVERIFY(validFile.open(QIODevice::ReadOnly)); + QSslKey validKey(validFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + QVERIFY(!validKey.isNull()); + + QTest::newRow("ValidClientCert:AutoVerifyPeer") << QSslSocket::AutoVerifyPeer << validCerts << validKey << true; + QTest::newRow("ValidClientCert:QueryPeer") << QSslSocket::QueryPeer << validCerts << validKey << true; + QTest::newRow("ValidClientCert:VerifyNone") << QSslSocket::VerifyNone << validCerts << validKey << true; + QTest::newRow("ValidClientCert:VerifyPeer") << QSslSocket::VerifyPeer << validCerts << validKey << true; + + // valid certificate, correct usage (client certificate), with chain + validCerts += QSslCertificate::fromPath(SRCDIR "certs/bogus-ca.crt"); + QCOMPARE(validCerts.size(), 2); + + QTest::newRow("ValidClientCert:AutoVerifyPeer") << QSslSocket::AutoVerifyPeer << validCerts << validKey << true; + QTest::newRow("ValidClientCert:QueryPeer") << QSslSocket::QueryPeer << validCerts << validKey << true; + QTest::newRow("ValidClientCert:VerifyNone") << QSslSocket::VerifyNone << validCerts << validKey << true; + QTest::newRow("ValidClientCert:VerifyPeer") << QSslSocket::VerifyPeer << validCerts << validKey << true; +} + +void tst_QSslSocket::verifyClientCertificate() +{ +#ifdef QT_SECURETRANSPORT + // We run both client and server on the same machine, + // this means, client can update keychain with client's certificates, + // and server later will use the same certificates from the same + // keychain thus making tests fail (wrong number of certificates, + // success instead of failure etc.). + QSKIP("This test can not work with Secure Transport"); +#endif + if (!QSslSocket::supportsSsl()) { + qWarning("SSL not supported, skipping test"); + return; + } + + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) + return; + + QFETCH(QSslSocket::PeerVerifyMode, peerVerifyMode); + SslServer server; + server.addCaCertificates = QLatin1String(SRCDIR "certs/bogus-ca.crt"); + server.ignoreSslErrors = false; + server.peerVerifyMode = peerVerifyMode; + QVERIFY(server.listen()); + + QEventLoop loop; + QTimer::singleShot(5000, &loop, SLOT(quit())); + + QFETCH(QList<QSslCertificate>, clientCerts); + QFETCH(QSslKey, clientKey); + QSslSocketPtr client(new QSslSocket); + client->setLocalCertificateChain(clientCerts); + client->setPrivateKey(clientKey); + socket = client.data(); + + connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); + connect(socket, SIGNAL(disconnected()), &loop, SLOT(quit())); + connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit())); + + client->connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); + + loop.exec(); + + QFETCH(bool, works); + QAbstractSocket::SocketState expectedState = (works) ? QAbstractSocket::ConnectedState : QAbstractSocket::UnconnectedState; + + // check server socket + QVERIFY(server.socket); + + QCOMPARE(int(server.socket->state()), int(expectedState)); + QCOMPARE(server.socket->isEncrypted(), works); + + if (peerVerifyMode == QSslSocket::VerifyNone || clientCerts.isEmpty()) { + QVERIFY(server.socket->peerCertificate().isNull()); + QVERIFY(server.socket->peerCertificateChain().isEmpty()); + } else { + QCOMPARE(server.socket->peerCertificate(), clientCerts.first()); + QCOMPARE(server.socket->peerCertificateChain(), clientCerts); + } + + // check client socket + QCOMPARE(int(client->state()), int(expectedState)); + QCOMPARE(client->isEncrypted(), works); +} + void tst_QSslSocket::setEmptyDefaultConfiguration() // this test should be last, as it has some side effects { // used to produce a crash in QSslConfigurationPrivate::deepCopyDefaultConfiguration, QTBUG-13265 @@ -2757,14 +2945,330 @@ void tst_QSslSocket::setEmptyDefaultConfiguration() // this test should be last, QSslConfiguration emptyConf; QSslConfiguration::setDefaultConfiguration(emptyConf); - QSslSocketPtr socket = newSocket(); - connect(socket.data(), SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); + QSslSocketPtr client = newSocket(); + socket = client.data(); + + connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); socket->connectToHostEncrypted(QtNetworkSettings::serverName(), 443); QFETCH_GLOBAL(bool, setProxy); if (setProxy && socket->waitForEncrypted(4000)) QSKIP("Skipping flaky test - See QTBUG-29941"); } +#ifndef QT_NO_OPENSSL +class PskProvider : public QObject +{ + Q_OBJECT + +public: + explicit PskProvider(QObject *parent = 0) + : QObject(parent) + { + } + + void setIdentity(const QByteArray &identity) + { + m_identity = identity; + } + + void setPreSharedKey(const QByteArray &psk) + { + m_psk = psk; + } + +public slots: + void providePsk(QSslPreSharedKeyAuthenticator *authenticator) + { + QVERIFY(authenticator); + QCOMPARE(authenticator->identityHint(), PSK_SERVER_IDENTITY_HINT); + 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); + } + } + +private: + QByteArray m_identity; + QByteArray m_psk; +}; + +void tst_QSslSocket::simplePskConnect_data() +{ + QTest::addColumn<PskConnectTestType>("pskTestType"); + QTest::newRow("PskConnectDoNotHandlePsk") << PskConnectDoNotHandlePsk; + QTest::newRow("PskConnectEmptyCredentials") << PskConnectEmptyCredentials; + QTest::newRow("PskConnectWrongCredentials") << PskConnectWrongCredentials; + QTest::newRow("PskConnectWrongIdentity") << PskConnectWrongIdentity; + QTest::newRow("PskConnectWrongPreSharedKey") << PskConnectWrongPreSharedKey; + QTest::newRow("PskConnectRightCredentialsPeerVerifyFailure") << PskConnectRightCredentialsPeerVerifyFailure; + QTest::newRow("PskConnectRightCredentialsVerifyPeer") << PskConnectRightCredentialsVerifyPeer; + QTest::newRow("PskConnectRightCredentialsDoNotVerifyPeer") << PskConnectRightCredentialsDoNotVerifyPeer; +} + +void tst_QSslSocket::simplePskConnect() +{ + QFETCH(PskConnectTestType, pskTestType); + QSKIP("This test requires change 1f8cab2c3bcd91335684c95afa95ae71e00a94e4 on the network test server, QTQAINFRA-917"); + + if (!QSslSocket::supportsSsl()) + QSKIP("No SSL support"); + + bool pskCipherFound = false; + const QList<QSslCipher> supportedCiphers = QSslSocket::supportedCiphers(); + foreach (const QSslCipher &cipher, supportedCiphers) { + if (cipher.name() == PSK_CIPHER_WITHOUT_AUTH) { + pskCipherFound = true; + break; + } + } + + if (!pskCipherFound) + QSKIP("SSL implementation does not support the necessary PSK cipher(s)"); + + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) + QSKIP("This test must not be going through a proxy"); + + QSslSocket socket; + this->socket = &socket; + + QSignalSpy connectedSpy(&socket, SIGNAL(connected())); + QVERIFY(connectedSpy.isValid()); + + QSignalSpy hostFoundSpy(&socket, SIGNAL(hostFound())); + QVERIFY(hostFoundSpy.isValid()); + + QSignalSpy disconnectedSpy(&socket, SIGNAL(disconnected())); + QVERIFY(disconnectedSpy.isValid()); + + QSignalSpy connectionEncryptedSpy(&socket, SIGNAL(encrypted())); + QVERIFY(connectionEncryptedSpy.isValid()); + + QSignalSpy sslErrorsSpy(&socket, SIGNAL(sslErrors(QList<QSslError>))); + QVERIFY(sslErrorsSpy.isValid()); + + QSignalSpy socketErrorsSpy(&socket, SIGNAL(error(QAbstractSocket::SocketError))); + QVERIFY(socketErrorsSpy.isValid()); + + QSignalSpy peerVerifyErrorSpy(&socket, SIGNAL(peerVerifyError(QSslError))); + QVERIFY(peerVerifyErrorSpy.isValid()); + + QSignalSpy pskAuthenticationRequiredSpy(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*))); + QVERIFY(pskAuthenticationRequiredSpy.isValid()); + + connect(&socket, SIGNAL(connected()), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(disconnected()), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(modeChanged(QSslSocket::SslMode)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(encrypted()), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(peerVerifyError(QSslError)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(exitLoop())); + + // force a PSK cipher w/o auth + socket.setCiphers(PSK_CIPHER_WITHOUT_AUTH); + + PskProvider provider; + + switch (pskTestType) { + case PskConnectDoNotHandlePsk: + // don't connect to the provider + break; + + case PskConnectEmptyCredentials: + // connect to the psk provider, but don't actually provide any PSK nor identity + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + break; + + case PskConnectWrongCredentials: + // provide totally wrong credentials + provider.setIdentity(PSK_CLIENT_IDENTITY.left(PSK_CLIENT_IDENTITY.length() - 1)); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY.left(PSK_CLIENT_PRESHAREDKEY.length() - 1)); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + break; + + case PskConnectWrongIdentity: + // right PSK, wrong identity + provider.setIdentity(PSK_CLIENT_IDENTITY.left(PSK_CLIENT_IDENTITY.length() - 1)); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + break; + + case PskConnectWrongPreSharedKey: + // right identity, wrong PSK + provider.setIdentity(PSK_CLIENT_IDENTITY); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY.left(PSK_CLIENT_PRESHAREDKEY.length() - 1)); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + break; + + case PskConnectRightCredentialsPeerVerifyFailure: + // right identity, right PSK, but since we can't verify the other peer, we'll fail + provider.setIdentity(PSK_CLIENT_IDENTITY); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + break; + + case PskConnectRightCredentialsVerifyPeer: + // right identity, right PSK, verify the peer (but ignore the failure) and establish the connection + provider.setIdentity(PSK_CLIENT_IDENTITY); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + connect(&socket, SIGNAL(peerVerifyError(QSslError)), this, SLOT(ignoreErrorSlot())); + break; + + case PskConnectRightCredentialsDoNotVerifyPeer: + // right identity, right PSK, do not verify the peer and establish the connection + provider.setIdentity(PSK_CLIENT_IDENTITY); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + socket.setPeerVerifyMode(QSslSocket::VerifyNone); + break; + } + + // check the peer verification mode + switch (pskTestType) { + case PskConnectDoNotHandlePsk: + case PskConnectEmptyCredentials: + case PskConnectWrongCredentials: + case PskConnectWrongIdentity: + case PskConnectWrongPreSharedKey: + case PskConnectRightCredentialsPeerVerifyFailure: + case PskConnectRightCredentialsVerifyPeer: + QCOMPARE(socket.peerVerifyMode(), QSslSocket::AutoVerifyPeer); + break; + + case PskConnectRightCredentialsDoNotVerifyPeer: + QCOMPARE(socket.peerVerifyMode(), QSslSocket::VerifyNone); + break; + } + + // Start connecting + socket.connectToHost(QtNetworkSettings::serverName(), PSK_SERVER_PORT); + QCOMPARE(socket.state(), QAbstractSocket::HostLookupState); + enterLoop(10); + + // Entered connecting state + QCOMPARE(socket.state(), QAbstractSocket::ConnectingState); + QCOMPARE(connectedSpy.count(), 0); + QCOMPARE(hostFoundSpy.count(), 1); + QCOMPARE(disconnectedSpy.count(), 0); + enterLoop(10); + + // Entered connected state + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QCOMPARE(socket.mode(), QSslSocket::UnencryptedMode); + QVERIFY(!socket.isEncrypted()); + QCOMPARE(connectedSpy.count(), 1); + QCOMPARE(hostFoundSpy.count(), 1); + QCOMPARE(disconnectedSpy.count(), 0); + + // Enter encrypted mode + socket.startClientEncryption(); + QCOMPARE(socket.mode(), QSslSocket::SslClientMode); + QVERIFY(!socket.isEncrypted()); + QCOMPARE(connectionEncryptedSpy.count(), 0); + QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(peerVerifyErrorSpy.count(), 0); + + // Start handshake. + enterLoop(10); + + // We must get the PSK signal in all cases + QCOMPARE(pskAuthenticationRequiredSpy.count(), 1); + + switch (pskTestType) { + case PskConnectDoNotHandlePsk: + case PskConnectEmptyCredentials: + case PskConnectWrongCredentials: + case PskConnectWrongIdentity: + case PskConnectWrongPreSharedKey: + // Handshake failure + QCOMPARE(socketErrorsSpy.count(), 1); + QCOMPARE(qvariant_cast<QAbstractSocket::SocketError>(socketErrorsSpy.at(0).at(0)), QAbstractSocket::SslHandshakeFailedError); + QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(peerVerifyErrorSpy.count(), 0); + QCOMPARE(connectionEncryptedSpy.count(), 0); + QVERIFY(!socket.isEncrypted()); + break; + + case PskConnectRightCredentialsPeerVerifyFailure: + // Peer verification failure + QCOMPARE(socketErrorsSpy.count(), 1); + QCOMPARE(qvariant_cast<QAbstractSocket::SocketError>(socketErrorsSpy.at(0).at(0)), QAbstractSocket::SslHandshakeFailedError); + QCOMPARE(sslErrorsSpy.count(), 1); + QCOMPARE(peerVerifyErrorSpy.count(), 1); + QCOMPARE(connectionEncryptedSpy.count(), 0); + QVERIFY(!socket.isEncrypted()); + break; + + case PskConnectRightCredentialsVerifyPeer: + // Peer verification failure, but ignore it and keep connecting + QCOMPARE(socketErrorsSpy.count(), 0); + QCOMPARE(sslErrorsSpy.count(), 1); + QCOMPARE(peerVerifyErrorSpy.count(), 1); + QCOMPARE(connectionEncryptedSpy.count(), 1); + QVERIFY(socket.isEncrypted()); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + break; + + case PskConnectRightCredentialsDoNotVerifyPeer: + // No peer verification => no failure + QCOMPARE(socketErrorsSpy.count(), 0); + QCOMPARE(sslErrorsSpy.count(), 0); + QCOMPARE(peerVerifyErrorSpy.count(), 0); + QCOMPARE(connectionEncryptedSpy.count(), 1); + QVERIFY(socket.isEncrypted()); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + break; + } + + // check writing + switch (pskTestType) { + case PskConnectDoNotHandlePsk: + case PskConnectEmptyCredentials: + case PskConnectWrongCredentials: + case PskConnectWrongIdentity: + case PskConnectWrongPreSharedKey: + case PskConnectRightCredentialsPeerVerifyFailure: + break; + + case PskConnectRightCredentialsVerifyPeer: + case PskConnectRightCredentialsDoNotVerifyPeer: + socket.write("Hello from Qt TLS/PSK!"); + QVERIFY(socket.waitForBytesWritten()); + break; + } + + // disconnect + switch (pskTestType) { + case PskConnectDoNotHandlePsk: + case PskConnectEmptyCredentials: + case PskConnectWrongCredentials: + case PskConnectWrongIdentity: + case PskConnectWrongPreSharedKey: + case PskConnectRightCredentialsPeerVerifyFailure: + break; + + case PskConnectRightCredentialsVerifyPeer: + case PskConnectRightCredentialsDoNotVerifyPeer: + socket.disconnectFromHost(); + enterLoop(10); + break; + } + + QCOMPARE(socket.state(), QAbstractSocket::UnconnectedState); + QCOMPARE(disconnectedSpy.count(), 1); +} +#endif // QT_NO_OPENSSL + #endif // QT_NO_SSL QTEST_MAIN(tst_QSslSocket) |