311 lines
10 KiB
C++
311 lines
10 KiB
C++
|
/**
|
||
|
* Copyright 2015 Vineet Garg <albertvaka@gmail.com>
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License as
|
||
|
* published by the Free Software Foundation; either version 2 of
|
||
|
* the License or (at your option) version 3 or any later version
|
||
|
* accepted by the membership of KDE e.V. (or its successor approved
|
||
|
* by the membership of KDE e.V.), which shall act as a proxy
|
||
|
* defined in Section 14 of version 3 of the license.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "../core/backends/lan/server.h"
|
||
|
#include "../core/backends/lan/socketlinereader.h"
|
||
|
|
||
|
#include <QSslKey>
|
||
|
#include <QtCrypto>
|
||
|
#include <QTest>
|
||
|
#include <QTimer>
|
||
|
|
||
|
/*
|
||
|
* This class tests the behaviour of socket line reader when the connection if over ssl. Since SSL takes part below application layer,
|
||
|
* working of SocketLineReader should be same.
|
||
|
*/
|
||
|
class TestSslSocketLineReader : public QObject
|
||
|
{
|
||
|
Q_OBJECT
|
||
|
public Q_SLOTS:
|
||
|
void newPackage();
|
||
|
|
||
|
private Q_SLOTS:
|
||
|
|
||
|
void initTestCase();
|
||
|
void init();
|
||
|
void cleanup();
|
||
|
void cleanupTestCase();
|
||
|
|
||
|
void testTrustedDevice();
|
||
|
void testUntrustedDevice();
|
||
|
void testTrustedDeviceWithWrongCertificate();
|
||
|
|
||
|
|
||
|
private:
|
||
|
const int PORT = 7894;
|
||
|
QTimer mTimer;
|
||
|
QCA::Initializer mQcaInitializer;
|
||
|
QEventLoop mLoop;
|
||
|
QList<QByteArray> mPackages;
|
||
|
Server *mServer;
|
||
|
QSslSocket *mClientSocket;
|
||
|
SocketLineReader *mReader;
|
||
|
|
||
|
private:
|
||
|
void setSocketAttributes(QSslSocket* socket, QString deviceName);
|
||
|
};
|
||
|
|
||
|
void TestSslSocketLineReader::initTestCase()
|
||
|
{
|
||
|
mServer = new Server(this);
|
||
|
|
||
|
QVERIFY2(mServer->listen(QHostAddress::LocalHost, PORT), "Failed to create local tcp server");
|
||
|
|
||
|
mTimer.setInterval(10 * 1000);//Ten second is more enough for this test, just used this so that to break mLoop if stuck
|
||
|
mTimer.setSingleShot(true);
|
||
|
connect(&mTimer, SIGNAL(timeout()), &mLoop, SLOT(quit()));
|
||
|
|
||
|
mTimer.start();
|
||
|
}
|
||
|
|
||
|
void TestSslSocketLineReader::init()
|
||
|
{
|
||
|
mClientSocket = new QSslSocket(this);
|
||
|
mClientSocket->connectToHost(QHostAddress::LocalHost, PORT);
|
||
|
connect(mClientSocket, SIGNAL(connected()), &mLoop, SLOT(quit()));
|
||
|
|
||
|
mLoop.exec();
|
||
|
|
||
|
QVERIFY2(mClientSocket->isOpen(), "Could not connect to local tcp server");
|
||
|
}
|
||
|
|
||
|
void TestSslSocketLineReader::cleanup()
|
||
|
{
|
||
|
mClientSocket->disconnectFromHost();
|
||
|
delete mClientSocket;
|
||
|
}
|
||
|
|
||
|
void TestSslSocketLineReader::cleanupTestCase()
|
||
|
{
|
||
|
delete mServer;
|
||
|
}
|
||
|
|
||
|
void TestSslSocketLineReader::testTrustedDevice()
|
||
|
{
|
||
|
|
||
|
int maxAttemps = 5;
|
||
|
QCOMPARE(true, mServer->hasPendingConnections());
|
||
|
while(!mServer->hasPendingConnections() && maxAttemps > 0) {
|
||
|
--maxAttemps;
|
||
|
QTest::qSleep(1000);
|
||
|
}
|
||
|
|
||
|
QSslSocket *serverSocket = mServer->nextPendingConnection();
|
||
|
|
||
|
QVERIFY2(serverSocket != 0, "Null socket returned by server");
|
||
|
QVERIFY2(serverSocket->isOpen(), "Server socket already closed");
|
||
|
|
||
|
setSocketAttributes(serverSocket, QString("Test Server"));
|
||
|
setSocketAttributes(mClientSocket, QString("Test Client"));
|
||
|
|
||
|
serverSocket->setPeerVerifyName("Test Client");
|
||
|
serverSocket->setPeerVerifyMode(QSslSocket::VerifyPeer);
|
||
|
serverSocket->addCaCertificate(mClientSocket->localCertificate());
|
||
|
|
||
|
mClientSocket->setPeerVerifyName("Test Server");
|
||
|
mClientSocket->setPeerVerifyMode(QSslSocket::VerifyPeer);
|
||
|
mClientSocket->addCaCertificate(serverSocket->localCertificate());
|
||
|
|
||
|
connect(mClientSocket, SIGNAL(encrypted()), &mLoop, SLOT(quit()));
|
||
|
serverSocket->startServerEncryption();
|
||
|
mClientSocket->startClientEncryption();
|
||
|
mLoop.exec();
|
||
|
|
||
|
// Both client and server socket should be encrypted here and should have remote certificate because VerifyPeer is used
|
||
|
QVERIFY2(mClientSocket->isOpen(), "Client socket already closed");
|
||
|
QVERIFY2(serverSocket->isOpen(), "Server socket already closed");
|
||
|
QVERIFY2(mClientSocket->isEncrypted(), "Client is not encrypted");
|
||
|
QVERIFY2(serverSocket->isEncrypted(), "Server is not encrypted");
|
||
|
QVERIFY2(!mClientSocket->peerCertificate().isNull(), "Server certificate not received");
|
||
|
QVERIFY2(!serverSocket->peerCertificate().isNull(), "Client certificate not received");
|
||
|
|
||
|
QList<QByteArray> dataToSend;
|
||
|
dataToSend << "foobar\n" << "barfoo\n" << "foobar?\n" << "\n" << "barfoo!\n" << "panda\n";
|
||
|
Q_FOREACH(const QByteArray &line, dataToSend) {
|
||
|
mClientSocket->write(line);
|
||
|
}
|
||
|
mClientSocket->flush();
|
||
|
|
||
|
mPackages.clear();
|
||
|
|
||
|
mReader = new SocketLineReader(serverSocket, this);
|
||
|
connect(mReader, SIGNAL(readyRead()), this,SLOT(newPackage()));
|
||
|
mLoop.exec();
|
||
|
|
||
|
/* remove the empty line before compare */
|
||
|
dataToSend.removeOne("\n");
|
||
|
|
||
|
QCOMPARE(mPackages.count(), 5);//We expect 5 Packages
|
||
|
for(int x = 0;x < 5; ++x) {
|
||
|
QCOMPARE(mPackages[x], dataToSend[x]);
|
||
|
}
|
||
|
|
||
|
delete mReader;
|
||
|
}
|
||
|
|
||
|
void TestSslSocketLineReader::testUntrustedDevice()
|
||
|
{
|
||
|
int maxAttemps = 5;
|
||
|
QCOMPARE(true, mServer->hasPendingConnections());
|
||
|
while(!mServer->hasPendingConnections() && maxAttemps > 0) {
|
||
|
--maxAttemps;
|
||
|
QTest::qSleep(1000);
|
||
|
}
|
||
|
|
||
|
QSslSocket *serverSocket = mServer->nextPendingConnection();
|
||
|
|
||
|
QVERIFY2(serverSocket != 0, "Null socket returned by server");
|
||
|
QVERIFY2(serverSocket->isOpen(), "Server socket already closed");
|
||
|
|
||
|
setSocketAttributes(serverSocket, QString("Test Server"));
|
||
|
setSocketAttributes(mClientSocket, QString("Test Client"));
|
||
|
|
||
|
serverSocket->setPeerVerifyName("Test Client");
|
||
|
serverSocket->setPeerVerifyMode(QSslSocket::QueryPeer);
|
||
|
|
||
|
mClientSocket->setPeerVerifyName("Test Server");
|
||
|
mClientSocket->setPeerVerifyMode(QSslSocket::QueryPeer);
|
||
|
|
||
|
connect(mClientSocket, SIGNAL(encrypted()), &mLoop, SLOT(quit()));
|
||
|
serverSocket->startServerEncryption();
|
||
|
mClientSocket->startClientEncryption();
|
||
|
mLoop.exec();
|
||
|
|
||
|
QVERIFY2(mClientSocket->isOpen(), "Client socket already closed");
|
||
|
QVERIFY2(serverSocket->isOpen(), "Server socket already closed");
|
||
|
QVERIFY2(mClientSocket->isEncrypted(), "Client is not encrypted");
|
||
|
QVERIFY2(serverSocket->isEncrypted(), "Server is not encrypted");
|
||
|
QVERIFY2(!mClientSocket->peerCertificate().isNull(), "Server certificate not received");
|
||
|
QVERIFY2(!serverSocket->peerCertificate().isNull(), "Client certificate not received");
|
||
|
|
||
|
QList<QByteArray> dataToSend;
|
||
|
dataToSend << "foobar\n" << "barfoo\n" << "foobar?\n" << "\n" << "barfoo!\n" << "panda\n";
|
||
|
Q_FOREACH(const QByteArray &line, dataToSend) {
|
||
|
mClientSocket->write(line);
|
||
|
}
|
||
|
mClientSocket->flush();
|
||
|
|
||
|
mPackages.clear();
|
||
|
|
||
|
mReader = new SocketLineReader(serverSocket, this);
|
||
|
connect(mReader, SIGNAL(readyRead()), SLOT(newPackage()));
|
||
|
mLoop.exec();
|
||
|
|
||
|
/* remove the empty line before compare */
|
||
|
dataToSend.removeOne("\n");
|
||
|
|
||
|
QCOMPARE(mPackages.count(), 5);//We expect 5 Packages
|
||
|
for(int x = 0;x < 5; ++x) {
|
||
|
QCOMPARE(mPackages[x], dataToSend[x]);
|
||
|
}
|
||
|
|
||
|
delete mReader;
|
||
|
}
|
||
|
|
||
|
void TestSslSocketLineReader::testTrustedDeviceWithWrongCertificate()
|
||
|
{
|
||
|
int maxAttemps = 5;
|
||
|
while(!mServer->hasPendingConnections() && maxAttemps > 0) {
|
||
|
--maxAttemps;
|
||
|
QTest::qSleep(1000);
|
||
|
}
|
||
|
|
||
|
QSslSocket *serverSocket = mServer->nextPendingConnection();
|
||
|
|
||
|
QVERIFY2(serverSocket != 0, "Could not open a connection to the client");
|
||
|
|
||
|
setSocketAttributes(serverSocket, QString("Test Server"));
|
||
|
setSocketAttributes(mClientSocket, QString("Test Client"));
|
||
|
|
||
|
// Not adding other device certificate to list of CA certificate, and using VerifyPeer. This should lead to handshake failure
|
||
|
serverSocket->setPeerVerifyName("Test Client");
|
||
|
serverSocket->setPeerVerifyMode(QSslSocket::VerifyPeer);
|
||
|
|
||
|
mClientSocket->setPeerVerifyName("Test Server");
|
||
|
mClientSocket->setPeerVerifyMode(QSslSocket::VerifyPeer);
|
||
|
|
||
|
connect(serverSocket, SIGNAL(encrypted()), &mLoop, SLOT(quit())); // Encrypted signal should never be emitted
|
||
|
connect(mClientSocket, SIGNAL(encrypted()), &mLoop, SLOT(quit())); // Encrypted signal should never be emitted
|
||
|
connect(serverSocket, SIGNAL(disconnected()), &mLoop, SLOT(quit()));
|
||
|
connect(mClientSocket, SIGNAL(disconnected()), &mLoop, SLOT(quit()));
|
||
|
|
||
|
serverSocket->startServerEncryption();
|
||
|
mClientSocket->startClientEncryption();
|
||
|
mLoop.exec();
|
||
|
|
||
|
QVERIFY2(!serverSocket->isEncrypted(), "Server is encrypted, it should not");
|
||
|
QVERIFY2(!mClientSocket->isEncrypted(), "lient is encrypted, it should now");
|
||
|
|
||
|
if (serverSocket->state() != QAbstractSocket::UnconnectedState) mLoop.exec(); // Wait until serverSocket is disconnected, It should be in disconnected state
|
||
|
if (mClientSocket->state() != QAbstractSocket::UnconnectedState) mLoop.exec(); // Wait until mClientSocket is disconnected, It should be in disconnected state
|
||
|
|
||
|
QCOMPARE((int)mClientSocket->state(), 0);
|
||
|
QCOMPARE((int)serverSocket->state(), 0);
|
||
|
|
||
|
}
|
||
|
|
||
|
void TestSslSocketLineReader::newPackage()
|
||
|
{
|
||
|
if (!mReader->bytesAvailable()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int maxLoops = 5;
|
||
|
while(mReader->bytesAvailable() > 0 && maxLoops > 0) {
|
||
|
--maxLoops;
|
||
|
const QByteArray package = mReader->readLine();
|
||
|
if (!package.isEmpty()) {
|
||
|
mPackages.append(package);
|
||
|
}
|
||
|
|
||
|
if (mPackages.count() == 5) {
|
||
|
mLoop.exit();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TestSslSocketLineReader::setSocketAttributes(QSslSocket *socket, QString deviceName) {
|
||
|
|
||
|
QDateTime startTime = QDateTime::currentDateTime();
|
||
|
QDateTime endTime = startTime.addYears(10);
|
||
|
QCA::CertificateInfo certificateInfo;
|
||
|
certificateInfo.insert(QCA::CommonName,deviceName);
|
||
|
certificateInfo.insert(QCA::Organization,"KDE");
|
||
|
certificateInfo.insert(QCA::OrganizationalUnit,"Kde connect");
|
||
|
|
||
|
QCA::CertificateOptions certificateOptions(QCA::PKCS10);
|
||
|
certificateOptions.setSerialNumber(10);
|
||
|
certificateOptions.setInfo(certificateInfo);
|
||
|
certificateOptions.setValidityPeriod(startTime, endTime);
|
||
|
certificateOptions.setFormat(QCA::PKCS10);
|
||
|
|
||
|
QCA::PrivateKey privKey = QCA::KeyGenerator().createRSA(2048);
|
||
|
QSslCertificate certificate = QSslCertificate(QCA::Certificate(certificateOptions, privKey).toPEM().toLatin1());
|
||
|
|
||
|
socket->setPrivateKey(QSslKey(privKey.toPEM().toLatin1(), QSsl::Rsa));
|
||
|
socket->setLocalCertificate(certificate);
|
||
|
|
||
|
}
|
||
|
|
||
|
QTEST_GUILESS_MAIN(TestSslSocketLineReader)
|
||
|
|
||
|
#include "testsslsocketlinereader.moc"
|
||
|
|