view ui/downloader.cpp @ 28:e783fd99a9eb

Add public key parsing
author Andre Heinecke <aheinecke@intevation.de>
date Thu, 13 Mar 2014 12:01:33 +0000
parents 62cd56cea09b
children d8e93fa1fc93
line wrap: on
line source
#include "downloader.h"

#ifndef DOWNLOAD_SERVER
#define DOWNLOAD_SERVER "https://www.intevation.de"
#endif

#include <QFile>
#include <QDir>
#include <QDebug>
#include <QStandardPaths>
#include <QUuid>
#include <QApplication>

#include <polarssl/net.h>
#include <polarssl/ssl.h>
#include <polarssl/entropy.h>
#include <polarssl/ctr_drbg.h>
#include <polarssl/error.h>
#include <polarssl/certs.h>

#define MAX_SW_SIZE 10485760
#define MAX_LIST_SIZE 1048576

QString getErrorMsg(int ret)
{
    char errbuf[255];
    polarssl_strerror(ret, errbuf, 255);
    errbuf[254] = '\0'; /* Just to be sure */
    return QString::fromLatin1(errbuf);
}

Downloader::Downloader(QObject* parent, const QString& url,
                       const QByteArray& certificate,
                       const QDateTime& newestSW,
                       const QDateTime& newestList):
    QThread(parent),
    mUrl(url),
    mPinnedCert(certificate),
    mLastModSW(newestSW),
    mLastModList(newestList),
    mErrorState(NoError),
    mInitialized(false),
    mServerFD(-1)
{
    int ret = -1;

    memset(&mSSL, 0, sizeof(ssl_context));

    if (certificate.isEmpty()) {
        QFile certResource(":certs/kolab.org");
        certResource.open(QFile::ReadOnly);
        mPinnedCert = certResource.readAll();
        certResource.close();
    }

    ret = init();
    if (ret == 0) {
        mInitialized = true;
    } else {
        qDebug() << "Initialization error: " + getErrorMsg(ret);
    }
}

int Downloader::init()
{
    int ret = -1;
    QUuid uuid = QUuid::createUuid();
    QString personalString = QApplication::applicationName() + uuid.toString();
    QByteArray personalBa = personalString.toLocal8Bit();

    x509_crt_init(&mX509PinnedCert);
    entropy_init(&mEntropy);

    ret = ssl_init(&mSSL);
    if (ret != 0) {
        /* The only documented error is malloc failed */
        mErrorState = ErrUnknown;
        return ret;
    }

    /*
     * Initialize random generator.
     * Personalisation string, does not need to be random but
     * should be unique according to documentation.
     *
     * the ctr_drbg structure does not need to be freed explicitly.
     */
    ret = ctr_drbg_init(&mCtr_drbg, entropy_func, &mEntropy,
                        (const unsigned char*) personalBa.constData(),
                        personalBa.size());
    if (ret != 0) {
        ssl_free(&mSSL);
        mErrorState = ErrUnknown;
        return ret;
    }

    ret = x509_crt_parse(&mX509PinnedCert,
                         (const unsigned char*) mPinnedCert.constData(),
                         mPinnedCert.size());
    if (ret != 0){
        ssl_free(&mSSL);
        mErrorState = InvalidPinnedCertificate;
        return ret;
    }

    ssl_set_endpoint(&mSSL, SSL_IS_CLIENT);
    ssl_set_authmode(&mSSL, SSL_VERIFY_OPTIONAL);
    ssl_set_ca_chain(&mSSL, &mX509PinnedCert, NULL, NULL);
    ssl_set_renegotiation(&mSSL, SSL_RENEGOTIATION_DISABLED);
    ssl_set_rng(&mSSL, ctr_drbg_random, &mCtr_drbg);

    return 0;
}

Downloader::~Downloader() {
    x509_crt_free(&mX509PinnedCert);
    entropy_free(&mEntropy);
    if (mInitialized) {
        ssl_free(&mSSL);
    }
}

QString Downloader::getDataDirectory()
{
    QString candidate =
        QStandardPaths::writableLocation(QStandardPaths::DataLocation);

    if (candidate.isEmpty()) {
        qDebug() << "Could not find writeable locaction for me";
        return QString();
    }

    QDir cDir(candidate);

    if (!cDir.exists()) {
        if (!cDir.mkpath(candidate)) {
            qDebug() << "Could not create path to: " << candidate;
            return QString();
        }
    }
    return cDir.absolutePath();
}

int Downloader::establishSSLConnection() {
    int ret = -1;
    const x509_crt *peerCert;

    mErrorState = ErrUnknown;

    if (mServerFD == -1) {
        return -1;
    }

    ssl_set_bio(&mSSL, net_recv, &mServerFD,
                       net_send, &mServerFD);

    while ((ret = ssl_handshake(&mSSL)) != 0) {
        if (ret != POLARSSL_ERR_NET_WANT_READ &&
                ret != POLARSSL_ERR_NET_WANT_WRITE) {
            qDebug() << "SSL Handshake failed: "
                 << getErrorMsg(ret);
            return ret;
        }
    }

    ret = ssl_get_verify_result(&mSSL);

    if (ret != 0 ) {

        if( ( ret & BADCERT_EXPIRED ) != 0 )
            qDebug() << "server certificate has expired";

        if( ( ret & BADCERT_REVOKED ) != 0 )
            qDebug() << "server certificate has been revoked";

        if( ( ret & BADCERT_CN_MISMATCH ) != 0 )
            qDebug() << "CN mismatch";

        if( ( ret & BADCERT_NOT_TRUSTED ) != 0 )
            qDebug() << "self-signed or not signed by a trusted CA";

#ifdef RELEASE_BUILD
        return -1;
#endif
    }

    peerCert = ssl_get_peer_cert(&mSSL);

    if (!peerCert) {
        mErrorState = InvalidCertificate;
        qDebug() << "Failed to get peer cert";
        return -1;
    }

    if (peerCert->raw.len == 0 ||
        peerCert->raw.len != mX509PinnedCert.raw.len) {
        mErrorState = InvalidCertificate;
        qDebug() << "Certificate length mismatch";
        return -1;
    }

    /* You can never be sure what those c++ operators do..
    if (mPinnedCert != QByteArray::fromRawData(
                (const char*) peerCert->raw.p,
            peerCert->raw.len)) {
        qDebug() << "Certificate content mismatch";
    }
    */

    for (unsigned int i = 0; i < peerCert->raw.len; i++) {
        if (peerCert->raw.p[i] != mX509PinnedCert.raw.p[i]) {
            qDebug() << "Certificate content mismatch";
            mErrorState = InvalidCertificate;
            return -1;
        }
    }
    mErrorState = NoError;
    return 0;
}


void Downloader::run() {
    int ret;

    if (!mInitialized) {
        emit error(tr("Failed to initialize SSL Module."), ErrUnknown);
        return;
    }

    ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
            mUrl.port(443));
    if (ret != 0) {
        mErrorState = NoConnection;
        emit error(tr("Failed to connect to %1.").arg(mUrl.host()),
                mErrorState);
        return;
    }

    emit progress(tr("Connected"), 1, -1);

    ret = establishSSLConnection();
    if (ret != 0) {
        qDebug() << "SSL conncetion failed: " << getErrorMsg(ret);
        emit error(tr("Failed to connect to %1.").arg(mUrl.host()),
                mErrorState);
        return;
    }

    qDebug() << "Connected to: " << mUrl.host();
    // TODO

    emit progress(tr("Closing"), 1, -1);
    ssl_close_notify (&mSSL);

    if (mServerFD != -1) {
        net_close(mServerFD);
        mServerFD = -1;
    }
}

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