view ui/tests/downloadertest.cpp @ 999:daa9448b64f5

(issue90) Use certificate pinning and forced ciphersuites for curl
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 01 Sep 2014 19:49:54 +0200
parents 8fb1b912fd8e
children fe2c6666b462
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include "downloadertest.h"
#include "downloader.h"
#include "common.h"

#include <polarssl/ssl_ciphersuites.h>
#include <QTextStream>
#include <QFile>
#include <QVariant>
#include <unistd.h>

#define MAX_WAIT 20

#define SETUP_SPY \
    QSignalSpy newListAvailable(downloader,                                 \
            SIGNAL(newListAvailable(const QString&, const QDateTime&)));    \
    QSignalSpy newSoftwareAvailable(downloader,                             \
           SIGNAL(newSoftwareAvailable(const QString&, const QDateTime&))); \
    QSignalSpy errors(downloader, SIGNAL(error(const QString &,             \
                    SSLConnection::ErrorCode)));                            \

void DownloaderTest::startServer(QString additionalOptions)
{
    QFile serverConfig(serverConfigDir.path() + "/" + "hiawatha.conf");
    QFile mimeConfig(serverConfigDir.path() + "/" + "mimetype.conf");
    if (serverProc.state() == QProcess::Running) {
        serverProc.close();
    }
    serverConfig.open(QIODevice::WriteOnly);
    mimeConfig.open(QIODevice::WriteOnly);
    mimeConfig.close(); /* just an empty file */

    QTextStream configStream(&serverConfig);
    configStream <<
        "Binding { " << endl <<
        "Port = 44443 " << endl <<
        "SSLcertFile = " << SOURCE_DIR <<
        "/data/valid_ssl_bp_priv.pem" << endl <<
        "Interface = 127.0.0.1" << endl <<
        "}" << endl <<
        "Hostname = 127.0.0.1" << endl <<
        "WebsiteRoot = " << serverConfigDir.path() << endl;
    configStream.flush();

    serverConfig.close();
    serverProc.start();
    serverProc.waitForStarted();
}

void DownloaderTest::initTestCase() {
    QStringList arguments;
    serverProc.setProgram(HIAWATHA_EXECUTABLE);
    arguments << "-d" << "-c" << serverConfigDir.path();
    serverProc.setArguments(arguments);
    qRegisterMetaType<SSLConnection::ErrorCode>("SSLConnection::ErrorCode");
    startServer();
    garbageInfo = QFileInfo(getRandomDataFile(2 * 1024, serverConfigDir.path()));
    QVERIFY(QFile::copy(QString::fromLocal8Bit(SOURCE_DIR"/data/list-valid.txt"),
        serverConfigDir.path() + "/" + "list-valid.txt"));

    QTest::qWait(1000); /* Wait for the server to settle */
}

void DownloaderTest::downloaderError(const QString &message, SSLConnection::ErrorCode error)
{
    qDebug() << "Downloader Error: " << error << " Msg: " << message;
}

bool filesEqual(QString filename1, QString filename2)
{
    bool retval = false;
    QFile file1(filename1);
    QFile file2(filename2);
    file1.open(QIODevice::ReadOnly);
    file2.open(QIODevice::ReadOnly);

    retval = (file1.readAll() == file2.readAll());
    file1.close();
    file2.close();
    return retval;
}

void DownloaderTest::testOtherCertificate()
{
    QFile otherCert(QString::fromLatin1(SOURCE_DIR) +
            "/data/valid_ssl_rsa.pem");
    otherCert.open(QIODevice::ReadOnly);

    QFileInfo fi(getRandomDataFile(200));

    Downloader* downloader = new Downloader(this,
            QString::fromLatin1("https://127.0.0.1:44443"),
            otherCert.readAll(),
            QDateTime::currentDateTime(), // Last installed SW
            QDateTime::fromString("2010", "YYYY"),
            "/" + garbageInfo.fileName(),
            "/list-valid.txt");
    otherCert.close();

    SETUP_SPY

    downloader->start();

    int waited = 0;
    while (errors.count() == 0  && waited < MAX_WAIT) {
        QTest::qWait(200);
        waited++;
    }
    QVERIFY(waited != MAX_WAIT);
    QVERIFY(newListAvailable.count() == 0);
    QVERIFY(newSoftwareAvailable.count() == 0);

    QList<QVariant> arguments = errors.takeFirst();

    int error = arguments.at(1).toInt();

    QVERIFY(error == SSLConnection::InvalidCertificate);
}

void DownloaderTest::testNoConnection()
{
    Downloader* downloader = new Downloader(this,
            QString::fromLatin1("https://foobar.intevation.de"));

    SETUP_SPY

    downloader->start();

    int waited = 0;
    while (newListAvailable.count() == 0 &&
            errors.count() == 0  && waited < MAX_WAIT) {
        QTest::qWait(200);
        waited++;
    }
    QVERIFY(waited != MAX_WAIT);
    QVERIFY(newListAvailable.count() == 0);
    QVERIFY(newSoftwareAvailable.count() == 0);

    QList<QVariant> arguments = errors.takeFirst();

    int error = arguments.at(1).toInt();

    QVERIFY(error == SSLConnection::NoConnection);
}
static int accept_ciphers[] = {
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
    TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
    TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
    0
};

void DownloaderTest::testForcedCiphers()
{
    Downloader* downloader = new Downloader(this,
            QString::fromLatin1(SERVER_URL),
            QByteArray(), /* Use default testing certificate */
            QDateTime::currentDateTime(),
            QDateTime::fromString("2010", "YYYY"),
            "/users/aheinecke/development/TrustBridge-development.exe",
            "/users/aheinecke/development/zertifikatsliste.txt");

    downloader->setCiphersuites(accept_ciphers);

    SETUP_SPY

    downloader->start();

    int waited = 0;
    while (errors.count() == 0  && waited < MAX_WAIT) {
        QTest::qWait(200);
        waited++;
    }
    QVERIFY(waited != MAX_WAIT);
    QVERIFY(newListAvailable.count() == 0);
    QVERIFY(newSoftwareAvailable.count() == 0);

    QList<QVariant> arguments = errors.takeFirst();

    int error = arguments.at(1).toInt();

    QVERIFY(error == SSLConnection::SSLHandshakeFailed);
}

void DownloaderTest::testNewSoftware()
{
    QFile validCert(QString::fromLatin1(SOURCE_DIR) +
            "/data/valid_ssl_bp.pem");
    validCert.open(QIODevice::ReadOnly);

    Downloader* downloader = new Downloader(this,
            QString::fromLatin1("https://127.0.0.1:44443"),
            validCert.readAll(),
            QDateTime::fromString("2010", "YYYY"),
            QDateTime::currentDateTime(),
            "/" + garbageInfo.fileName(),
            "/list-valid.txt");
    validCert.close();

    SETUP_SPY

    connect(downloader, SIGNAL(error(const QString &, SSLConnection::ErrorCode)),
            this, SLOT(downloaderError(const QString &, SSLConnection::ErrorCode)));

    downloader->start();

    int waited = 0;
    while (newSoftwareAvailable.count() == 0 &&
            errors.count() == 0  && waited < 20) {
        QTest::qWait(200);
        waited++;
    }
    QVERIFY(waited != 20);
    QVERIFY(newListAvailable.count() == 0);
    QVERIFY(newSoftwareAvailable.count() == 1);
    QVERIFY(errors.count() == 0);

    QList<QVariant> arguments = newSoftwareAvailable.takeFirst();

    QVERIFY(filesEqual(serverConfigDir.path() + "/" + garbageInfo.fileName(),
        arguments.at(0).toString()));

    delete downloader;
}

void DownloaderTest::testDefaultUrl()
{
    /* This test might fail if SERVER_URL is configured differently
     * and needs to be adapted once there is a public download server. 
     * This test also needs internet connectivity. */
    Downloader* downloader = new Downloader(this,
            QString::fromLatin1(SERVER_URL),
            QByteArray(),
            QDateTime::currentDateTime(), // Last installed SW
            QDateTime::fromString("2010", "YYYY"),
            QString("/users/aheinecke/zertifikatsliste.txt"),
            QString("/users/aheinecke/zertifikatsliste.txt"));

    SETUP_SPY

    connect(downloader, SIGNAL(error(const QString &, SSLConnection::ErrorCode)),
            this, SLOT(downloaderError(const QString &, SSLConnection::ErrorCode)));

    downloader->start();

    int waited = 0;
    while (newListAvailable.count() == 0 &&
            errors.count() == 0  && waited < 20) {
        QTest::qWait(200);
        waited++;
    }
    QVERIFY(waited != 20);
    QVERIFY(newListAvailable.count() == 1);
    QVERIFY(newSoftwareAvailable.count() == 0);
    QVERIFY(errors.count() == 0);

    delete downloader;
}

void DownloaderTest::testNewList()
{
    QFile validCert(QString::fromLatin1(SOURCE_DIR) +
            "/data/valid_ssl_bp.pem");
    validCert.open(QIODevice::ReadOnly);

    Downloader* downloader = new Downloader(this,
            QString::fromLatin1("https://127.0.0.1:44443"),
            validCert.readAll(),
            QDateTime::currentDateTime(), // Last installed SW
            QDateTime::fromString("2010", "YYYY"),
            "/" + garbageInfo.fileName(),
            "/list-valid.txt");
    validCert.close();

    SETUP_SPY

    connect(downloader, SIGNAL(error(const QString &, SSLConnection::ErrorCode)),
            this, SLOT(downloaderError(const QString &, SSLConnection::ErrorCode)));

    downloader->start();

    int waited = 0;
    while (newListAvailable.count() == 0 &&
            errors.count() == 0  && waited < 20) {
        QTest::qWait(200);
        waited++;
    }
    QVERIFY(waited != 20);
    QVERIFY(newListAvailable.count() == 1);
    QVERIFY(newSoftwareAvailable.count() == 0);
    QVERIFY(errors.count() == 0);

    QList<QVariant> arguments = newListAvailable.takeFirst();

    QVERIFY(filesEqual(QString::fromLatin1(SOURCE_DIR) + "/data/list-valid.txt",
        arguments.at(0).toString()));

    delete downloader;
}

void DownloaderTest::cleanupTestCase() {
    if (serverProc.state() == QProcess::Running) {
        serverProc.close();
    }
}

QTEST_GUILESS_MAIN (DownloaderTest);

http://wald.intevation.org/projects/trustbridge/