changeset 45:c6125d73faf4

Move SSLConnection into it's own class
author Andre Heinecke <aheinecke@intevation.de>
date Fri, 14 Mar 2014 16:40:53 +0000
parents b3e8e047bc2c
children d28e2624c1d5
files ui/CMakeLists.txt ui/downloader.cpp ui/downloader.h ui/mainwindow.cpp ui/mainwindow.h ui/sslconnection.cpp ui/sslconnection.h ui/tests/CMakeLists.txt ui/tests/downloadertest.cpp ui/tests/downloadertest.h
diffstat 10 files changed, 420 insertions(+), 354 deletions(-) [+]
line wrap: on
line diff
--- a/ui/CMakeLists.txt	Fri Mar 14 16:06:40 2014 +0000
+++ b/ui/CMakeLists.txt	Fri Mar 14 16:40:53 2014 +0000
@@ -11,13 +11,18 @@
     ${CMAKE_SOURCE_DIR}/common/listutil.c
 )
 
-set(M13UI_SOURCES
-    ${CMAKE_CURRENT_SOURCE_DIR}/mainwindow.cpp
+set(DOWNLOADER_SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/downloader.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/downloader_win.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/downloader_linux.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/sslconnection.cpp
+)
+
+set(M13UI_SOURCES
+    ${CMAKE_CURRENT_SOURCE_DIR}/mainwindow.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
     ${CERTIFICATELIST_SOURCES}
+    ${DOWNLOADER_SOURCES}
 )
 
 # Seperated to make it easier to include the sources in tests
--- a/ui/downloader.cpp	Fri Mar 14 16:06:40 2014 +0000
+++ b/ui/downloader.cpp	Fri Mar 14 16:40:53 2014 +0000
@@ -8,8 +8,6 @@
 #include <QDir>
 #include <QDebug>
 #include <QStandardPaths>
-#include <QUuid>
-#include <QApplication>
 #include <QTextStream>
 #include <QLocale>
 #include <QSaveFile>
@@ -23,126 +21,28 @@
 
 #define MAX_SW_SIZE 10485760
 #define MAX_LIST_SIZE 1048576
-#define MAX_IO_TRIES 10
 
 #define LIST_RESOURCE "/incoming/aheinecke/test"
 #define SW_RESOURCE "/incoming/aheinecke/test"
 
-/* TODO: Wrap ssl_session in a class for reuse.
- * see programs/ssl/ssl_client2.c for example of session reuse */
-
-#ifdef CONNECTION_DEBUG
-static void my_debug(void *ctx, int level, const char *str)
-{
-    fprintf((FILE *) ctx, "%s", str);
-    fflush((FILE *) ctx);
-}
-#endif
-
-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):
+                       const QDateTime& newestList,
+                       const QString& resourceSW,
+                       const QString& resourceList):
     QThread(parent),
-    mUrl(url),
-    mPinnedCert(certificate),
     mLastModSW(newestSW),
     mLastModList(newestList),
-    mErrorState(NoError),
-    mInitialized(false),
-    mServerFD(-1)
+    mResourceSW(resourceSW),
+    mResourceList(resourceList),
+    mSSLConnection(url, certificate)
 {
-    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);
-#ifdef RELEASE_BUILD
-    ssl_set_min_version(&mSSL, SSL_MAJOR_VERSION_3, SSL_MINOR_VERSION_3);
-#endif
-
-#ifdef CONNECTION_DEBUG
-    ssl_set_dbg(&mSSL, my_debug, stdout);
-#endif
-
-    return 0;
-}
 
 Downloader::~Downloader() {
-    x509_crt_free(&mX509PinnedCert);
-    entropy_free(&mEntropy);
-    if (mInitialized) {
-        ssl_free(&mSSL);
-    }
 }
 
 QString Downloader::getDataDirectory()
@@ -166,153 +66,6 @@
     return cDir.absolutePath();
 }
 
-int Downloader::establishSSLConnection() {
-    int ret = -1;
-    const x509_crt *peerCert;
-
-    if (mServerFD == -1 || !mInitialized) {
-        mErrorState = ErrUnknown;
-        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);
-            mErrorState = SSLHandshakeFailed;
-            return ret;
-        }
-    }
-
-    /* we might want to set the verify function
-     * with ssl_set_verify before to archive the
-     * certificate pinning. */
-
-    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";
-        ret = -1;
-#ifdef RELEASE_BUILD
-        mErrorState = InvalidCertificate;
-        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;
-        }
-    }
-    return 0;
-}
-
-/* Helper around polarssl bare bone api */
-int polarSSLWrite (ssl_context *ssl, const QByteArray& request)
-{
-    unsigned int tries = 0;
-    int ret = -1;
-
-    const unsigned char *buf = (const unsigned char *) request.constData();
-    size_t len = (size_t) request.size();
-
-    qDebug() << "Seinding request: " << request;
-    /* According to doc for ssl_write:
-     *
-     * When this function returns POLARSSL_ERR_NET_WANT_WRITE,
-     * it must be called later with the same arguments,
-     * until it returns a positive value.
-     */
-    do {
-        ret = ssl_write(ssl, buf, len);
-        if (ret >= 0) {
-            if ((unsigned int) ret == len) {
-                return 0;
-            } else {
-                qDebug() << "Write failed to write everything";
-                return -1;
-            }
-        }
-
-        if (ret != POLARSSL_ERR_NET_WANT_WRITE) {
-            return ret;
-        }
-        tries++;
-        net_usleep(100000); /* sleep 100ms to give the socket a chance
-                               to clean up. */
-    } while (tries < MAX_IO_TRIES);
-
-    return ret;
-}
-
-/* Helper around polarssl bare bone api read at most len bytes
- * and return them as a byte array returns a NULL byte array on error*/
-QByteArray polarSSLRead (ssl_context *ssl, size_t len)
-{
-    unsigned char buf[len];
-    QByteArray retval("");
-    int ret = -1;
-
-    do {
-        memset (buf, 0, sizeof(buf));
-        ret = ssl_read(ssl, buf, len);
-        if (ret == 0 ||
-            ret == POLARSSL_ERR_SSL_CONN_EOF) {
-            /* EOF */
-            return retval;
-        }
-        if (ret <= 0) {
-            qDebug() << "Read failed: " << getErrorMsg(ret);
-            return QByteArray();
-        }
-        if (len < (len - (unsigned int) ret)) {
-            /* Should never happen if ssl_read behaves */
-            qDebug() << "integer overflow in polarSSLRead";
-            return QByteArray();
-        }
-        len -= (unsigned int) ret;
-        retval.append((const char *)buf, len);
-    } while (len > 0);
-
-    return retval;
-}
-
 
 QDateTime Downloader::getLastModifiedHeader(const QString &resource) {
     int ret = -1;
@@ -324,17 +77,17 @@
                 "Connection: Keep-Alive\r\n"
                 "\r\n\r\n").arg(resource);
 
-    ret = polarSSLWrite (&mSSL, headRequest.toUtf8());
+    ret = mSSLConnection.write(headRequest.toUtf8());
     if (ret != 0) {
-        mErrorState = ConnectionLost;
+        emit error (tr("Connection lost"), SSLConnection::ConnectionLost);
         return QDateTime();
     }
 
-    response = polarSSLRead(&mSSL, 1024);
+    response = mSSLConnection.read(1024);
 
     if (response.isNull()) {
         qDebug() << "No response";
-        mErrorState = ConnectionLost;
+        emit error (tr("Connection lost"), SSLConnection::ConnectionLost);
         return QDateTime();
     }
 
@@ -352,7 +105,7 @@
         }
     }
 
-    mErrorState = InvalidResponse;
+    emit error (tr("Invalid response from the server"), SSLConnection::InvalidResponse);
     return QDateTime();
 }
 
@@ -367,7 +120,7 @@
 
     QSaveFile outputFile(fileName);
 
-    ret = polarSSLWrite (&mSSL, getRequest.toUtf8());
+    ret = mSSLConnection.write(getRequest.toUtf8());
 
     // Open / Create the file to write to.
     if (!outputFile.open(QIODevice::WriteOnly)) {
@@ -376,15 +129,15 @@
     }
 
     if (ret != 0) {
-        mErrorState = ConnectionLost;
+        emit error(tr("Connection lost"), SSLConnection::ConnectionLost);
         return false;
     }
 
     do {
-        QByteArray response = polarSSLRead(&mSSL, 8192);
+        QByteArray response = mSSLConnection.read(8192);
         if (response.isNull()) {
             qDebug() << "Error reading response";
-            mErrorState = ConnectionLost;
+            emit error(tr("Connection lost"), SSLConnection::ConnectionLost);
             return false;
         }
         if (response.isEmpty()) {
@@ -404,33 +157,23 @@
     QDateTime remoteModList;
     QDateTime remoteModSW;
 
-    if (!mInitialized) {
-        emit error(tr("Failed to initialize SSL Module."), ErrUnknown);
+    if (!mSSLConnection.initialized()) {
+        emit error(tr("Failed to initialize SSL Module."), SSLConnection::ErrUnknown);
         return;
     }
 
-    ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
-                      mUrl.port(443));
+    ret = mSSLConnection.connect();
 
     if (ret != 0) {
-        mErrorState = NoConnection;
-        emit error(tr("Failed to connect to %1.").arg(mUrl.host()),
-                mErrorState);
+        emit error(tr("Failed to connect."),
+                   mSSLConnection.getLastError());
         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;
-    }
-
-    remoteModSW = getLastModifiedHeader(QString::fromLatin1("/incoming/aheinecke/test"));
-    remoteModList = getLastModifiedHeader(QString::fromLatin1("/incoming/aheinecke/test"));
+    remoteModSW = getLastModifiedHeader(mResourceSW);
+    remoteModList = getLastModifiedHeader(mResourceList);
 
     if (!remoteModSW.isValid() || !remoteModList.isValid()) {
         qDebug() << "Could not read headers";
@@ -480,10 +223,4 @@
     }
 
     emit progress(tr("Closing"), 1, -1);
-    ssl_close_notify (&mSSL);
-
-    if (mServerFD != -1) {
-        net_close(mServerFD);
-        mServerFD = -1;
-    }
 }
--- a/ui/downloader.h	Fri Mar 14 16:06:40 2014 +0000
+++ b/ui/downloader.h	Fri Mar 14 16:40:53 2014 +0000
@@ -7,15 +7,11 @@
  */
 
 #include <QThread>
+#include <QDateTime>
 #include <QString>
 #include <QByteArray>
-#include <QDateTime>
-#include <QUrl>
 
-#include <polarssl/x509_crt.h>
-#include <polarssl/entropy.h>
-#include <polarssl/ctr_drbg.h>
-#include <polarssl/ssl.h>
+#include "sslconnection.h"
 
 class Downloader: public QThread
 {
@@ -38,26 +34,18 @@
      * @param[in] certificate optional certificate to validate https connection
      * @param[in] newestSW datetime after which software should be downloaded
      * @param[in] newestList datetime after which the list should be downloaded
+     * @param[in] resourceSW the path where the software is to be found
+     * @param[in] resourceList the path where the list is to be found
      */
     Downloader(QObject* parent, const QString& url,
                const QByteArray& certificate = QByteArray(),
                const QDateTime& newestSW = QDateTime(),
-               const QDateTime& newestList = QDateTime());
+               const QDateTime& newestList = QDateTime(),
+               const QString& resourceSW = QString(),
+               const QString& resourceList = QString());
 
     ~Downloader();
 
-    enum ErrorCode {
-        NoError,
-        NoConnection,
-        SSLHandshakeFailed,
-        InvalidCertificate,
-        InvalidPinnedCertificate,
-        InvalidResponse,
-        ConnectionLost,
-        Timeout,
-        ErrUnknown
-    };
-
     /**
      * @brief get the directory where the downloader saves data
      *
@@ -73,50 +61,19 @@
      *
      * @returns The current error state.
      **/
-    ErrorCode getErrorState();
+    SSLConnection::ErrorCode getErrorState();
 
 protected:
     void run();
 
 private:
-    QUrl mUrl;
-    QByteArray mPinnedCert;
-    x509_crt mX509PinnedCert;
-    entropy_context mEntropy;
-    ctr_drbg_context mCtr_drbg;
-    ssl_context mSSL;
-
     QDateTime mLastModSW;
     QDateTime mLastModList;
 
-    /* Convienience to avoid having to parse all
-     * PolarSSL errors */
-    ErrorCode mErrorState;
-
-    bool mInitialized;
-    bool mConnectionEstablished;
-
-    int mServerFD;
-
+    QString mResourceSW;
+    QString mResourceList;
 
-    /* @brief: Initialize polarssl structures
-     *
-     * This wraps polarssl initialization functions
-     * that can return an error.
-     * Sets the error state accordingly.
-     *
-     * @returns: 0 on success a polarssl error otherwise.
-     */
-    int init();
-
-    /** @brief: Establish the ssl connection
-     *
-     * Modifies the error state. Before calling this the mServerFD should
-     * be set to a valid socket.
-     *
-     * @returns 0 on success otherwise a polarssl error or -1 is returned
-     */
-    int establishSSLConnection();
+    SSLConnection mSSLConnection;
 
     /** @brief get the last modified header of a resource.
      *
@@ -170,6 +127,6 @@
      * @param[out] message: A message to show. Can be empty.
      * @param[out] errorCode: ErrorCode of this error.
      */
-    void error(const QString &message, Downloader::ErrorCode error);
+    void error(const QString &message, SSLConnection::ErrorCode error);
 };
 #endif
--- a/ui/mainwindow.cpp	Fri Mar 14 16:06:40 2014 +0000
+++ b/ui/mainwindow.cpp	Fri Mar 14 16:40:53 2014 +0000
@@ -20,7 +20,7 @@
 MainWindow::MainWindow() {
     createActions();
     createTrayIcon();
-    qRegisterMetaType<Downloader::ErrorCode>("Downloader::ErrorCode");
+    qRegisterMetaType<SSLConnection::ErrorCode>("SSLConnection::ErrorCode");
 
     connect(mTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
             this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
@@ -132,13 +132,13 @@
     connect(downloader, SIGNAL(newSoftwareAvailable(const QString&, const QDateTime&)),
             this, SLOT(handleNewSW(const QString&, const QDateTime&)));
     connect(downloader, SIGNAL(finished()), downloader, SLOT(deleteLater()));
-    connect(downloader, SIGNAL(error(const QString &, Downloader::ErrorCode)),
-            this, SLOT(downloaderError(const QString &, Downloader::ErrorCode)));
+    connect(downloader, SIGNAL(error(const QString &, SSLConnection::ErrorCode)),
+            this, SLOT(downloaderError(const QString &, SSLConnection::ErrorCode)));
     downloader->start();
 }
 
 
-void MainWindow::downloaderError(const QString &message, Downloader::ErrorCode error)
+void MainWindow::downloaderError(const QString &message, SSLConnection::ErrorCode error)
 {
     mCurMessage = message;
     showMessage();
--- a/ui/mainwindow.h	Fri Mar 14 16:06:40 2014 +0000
+++ b/ui/mainwindow.h	Fri Mar 14 16:40:53 2014 +0000
@@ -36,7 +36,7 @@
     void checkUpdates();
     void handleNewList(const QString& fileName, const QDateTime& modDate);
     void handleNewSW(const QString& fileName, const QDateTime& modDate);
-    void downloaderError(const QString &message, Downloader::ErrorCode error);
+    void downloaderError(const QString &message, SSLConnection::ErrorCode error);
 
 private:
     void verifyAvailableData();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/sslconnection.cpp	Fri Mar 14 16:40:53 2014 +0000
@@ -0,0 +1,279 @@
+/* TODO: Wrap ssl_session in a class for reuse.
+ * see programs/ssl/ssl_client2.c for example of session reuse */
+#include "sslconnection.h"
+
+#include <QFile>
+#include <QUuid>
+#include <QApplication>
+
+#define MAX_IO_TRIES 10
+
+#ifdef CONNECTION_DEBUG
+static void my_debug(void *ctx, int level, const char *str)
+{
+    fprintf((FILE *) ctx, "%s", str);
+    fflush((FILE *) ctx);
+}
+#endif
+
+QString getErrorMsg(int ret)
+{
+    char errbuf[255];
+    polarssl_strerror(ret, errbuf, 255);
+    errbuf[254] = '\0'; /* Just to be sure */
+    return QString::fromLatin1(errbuf);
+}
+
+SSLConnection::SSLConnection(const QString& url,
+                             const QByteArray& certificate):
+    mUrl(url),
+    mPinnedCert(certificate),
+    mInitialized(false),
+    mConnected(false),
+    mServerFD(-1),
+    mErrorState(NoError)
+{
+    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 SSLConnection::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);
+#ifdef RELEASE_BUILD
+    ssl_set_min_version(&mSSL, SSL_MAJOR_VERSION_3, SSL_MINOR_VERSION_3);
+#endif
+
+#ifdef CONNECTION_DEBUG
+    ssl_set_dbg(&mSSL, my_debug, stdout);
+#endif
+
+    return 0;
+}
+
+SSLConnection::~SSLConnection() {
+    if (mConnected) {
+        ssl_close_notify (&mSSL);
+        if (mServerFD != -1) {
+            net_close(mServerFD);
+            mServerFD = -1;
+        }
+    }
+    x509_crt_free(&mX509PinnedCert);
+    entropy_free(&mEntropy);
+    if (mInitialized) {
+        ssl_free(&mSSL);
+    }
+}
+
+int SSLConnection::connect() {
+    int ret = -1;
+    const x509_crt *peerCert;
+
+    if (!mInitialized) {
+        mErrorState = ErrUnknown;
+        return -1;
+    }
+
+    ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
+                  mUrl.port(443));
+
+    if (ret != 0) {
+        mErrorState = NoConnection;
+        return ret;
+    }
+
+    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);
+            mErrorState = SSLHandshakeFailed;
+            return ret;
+        }
+    }
+
+    /* we might want to set the verify function
+     * with ssl_set_verify before to archive the
+     * certificate pinning. */
+
+    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";
+        ret = -1;
+#ifdef RELEASE_BUILD
+        mErrorState = InvalidCertificate;
+        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;
+        }
+    }
+    return 0;
+}
+
+int SSLConnection::write (const QByteArray& request)
+{
+    unsigned int tries = 0;
+    int ret = -1;
+
+    const unsigned char *buf = (const unsigned char *) request.constData();
+    size_t len = (size_t) request.size();
+
+    qDebug() << "Seinding request: " << request;
+    /* According to doc for ssl_write:
+     *
+     * When this function returns POLARSSL_ERR_NET_WANT_WRITE,
+     * it must be called later with the same arguments,
+     * until it returns a positive value.
+     */
+    do {
+        ret = ssl_write(&mSSL, buf, len);
+        if (ret >= 0) {
+            if ((unsigned int) ret == len) {
+                return 0;
+            } else {
+                qDebug() << "Write failed to write everything";
+                return -1;
+            }
+        }
+
+        if (ret != POLARSSL_ERR_NET_WANT_WRITE) {
+            return ret;
+        }
+        tries++;
+        net_usleep(100000); /* sleep 100ms to give the socket a chance
+                               to clean up. */
+    } while (tries < MAX_IO_TRIES);
+
+    return ret;
+}
+
+QByteArray SSLConnection::read(size_t len)
+{
+    unsigned char buf[len];
+    QByteArray retval("");
+    int ret = -1;
+
+    do {
+        memset (buf, 0, sizeof(buf));
+        ret = ssl_read(&mSSL, buf, len);
+        if (ret == 0 ||
+            ret == POLARSSL_ERR_SSL_CONN_EOF) {
+            /* EOF */
+            return retval;
+        }
+        if (ret <= 0) {
+            qDebug() << "Read failed: " << getErrorMsg(ret);
+            return QByteArray();
+        }
+        if (len < (len - (unsigned int) ret)) {
+            /* Should never happen if ssl_read behaves */
+            qDebug() << "integer overflow in polarSSLRead";
+            return QByteArray();
+        }
+        len -= (unsigned int) ret;
+        retval.append((const char *)buf, len);
+    } while (len > 0);
+
+    return retval;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/sslconnection.h	Fri Mar 14 16:40:53 2014 +0000
@@ -0,0 +1,88 @@
+#ifndef SSLCONNECTION_H
+#define SSLCONNECTION_H
+
+/**
+ * @file sslconnection.h
+ * @brief Qt wrapper around polarssl ssl api
+ */
+
+#include <QDebug>
+#include <QUrl>
+#include <QString>
+#include <QByteArray>
+
+#include <polarssl/entropy.h>
+#include <polarssl/net.h>
+#include <polarssl/ssl.h>
+#include <polarssl/ctr_drbg.h>
+#include <polarssl/error.h>
+#include <polarssl/certs.h>
+
+class SSLConnection
+{
+public:
+    enum ErrorCode {
+        NoError,
+        NoConnection,
+        SSLHandshakeFailed,
+        InvalidCertificate,
+        InvalidPinnedCertificate,
+        InvalidResponse,
+        ConnectionLost,
+        Timeout,
+        ErrUnknown
+    };
+
+    /**
+     * @brief Construct a pinned SSL Connection
+     *
+     * @param[in] url the Url to connect to
+     * @param[in] certificate optional certificate to validate https connection
+     */
+    SSLConnection(const QString& url,
+                  const QByteArray& certificate = QByteArray());
+
+    ~SSLConnection();
+
+    /** @brief write */
+    int write(const QByteArray& request);
+
+    /**
+     * @brief read at most len bytes
+     * and return them as a byte array returns a NULL byte array on error*/
+    QByteArray read(size_t len);
+
+    bool initialized() { return mInitialized; }
+    bool connected() { return mConnected; }
+
+    ErrorCode getLastError() { return mErrorState; }
+
+    /** @brief: Establish the connection
+     *
+     * @returns 0 on success otherwise a polarssl error or -1 is returned
+     */
+    int connect();
+
+private:
+    QUrl mUrl;
+    QByteArray mPinnedCert;
+    x509_crt mX509PinnedCert;
+    entropy_context mEntropy;
+    ctr_drbg_context mCtr_drbg;
+    ssl_context mSSL;
+    bool mInitialized;
+    bool mConnected;
+    int mServerFD;
+    SSLConnection::ErrorCode mErrorState;
+    /* @brief: Initialize polarssl structures
+     *
+     * This wraps polarssl initialization functions
+     * that can return an error.
+     * Sets the error state accordingly.
+     *
+     * @returns: 0 on success a polarssl error otherwise.
+     */
+    int init();
+};
+
+#endif
--- a/ui/tests/CMakeLists.txt	Fri Mar 14 16:06:40 2014 +0000
+++ b/ui/tests/CMakeLists.txt	Fri Mar 14 16:40:53 2014 +0000
@@ -25,6 +25,6 @@
 # so that it can be used in file names in the tests.
 add_definitions(-DSOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
 add_m13_test(certlistparsertest.cpp "${CERTIFICATELIST_SOURCES}")
-add_m13_test(downloadertest.cpp "${CMAKE_SOURCE_DIR}/ui/downloader.cpp")
+add_m13_test(downloadertest.cpp "${DOWNLOADER_SOURCES}")
 #add_m13_test(${CMAKE_SOURCE_DIR}/ui/main.cpp "${M13UI_SOURCES}")
 
--- a/ui/tests/downloadertest.cpp	Fri Mar 14 16:06:40 2014 +0000
+++ b/ui/tests/downloadertest.cpp	Fri Mar 14 16:40:53 2014 +0000
@@ -39,12 +39,12 @@
     serverProc.setProgram(HIAWATHA_EXECUTABLE);
     arguments << "-d" << "-c" << serverConfigDir.path();
     serverProc.setArguments(arguments);
-    qRegisterMetaType<Downloader::ErrorCode>("Downloader::ErrorCode");
+    qRegisterMetaType<SSLConnection::ErrorCode>("SSLConnection::ErrorCode");
     startServer();
     QTest::qWait(1000); /* Wait for the server to settle */
 }
 
-void DownloaderTest::downloaderError(const QString &message, Downloader::ErrorCode error)
+void DownloaderTest::downloaderError(const QString &message, SSLConnection::ErrorCode error)
 {
     qDebug() << "Downloader Error: " << error << " Msg: " << message;
 }
@@ -69,9 +69,9 @@
     QSignalSpy newSoftwareAvailable(downloader,
            SIGNAL(newSoftwareAvailable(const QString&, const QDateTime&)));
     QSignalSpy errors(downloader, SIGNAL(error(const QString &,
-                    Downloader::ErrorCode)));
-    connect(downloader, SIGNAL(error(const QString &, Downloader::ErrorCode)),
-            this, SLOT(downloaderError(const QString &, Downloader::ErrorCode)));
+                    SSLConnection::ErrorCode)));
+    connect(downloader, SIGNAL(error(const QString &, SSLConnection::ErrorCode)),
+            this, SLOT(downloaderError(const QString &, SSLConnection::ErrorCode)));
 
     downloader->start();
 
--- a/ui/tests/downloadertest.h	Fri Mar 14 16:06:40 2014 +0000
+++ b/ui/tests/downloadertest.h	Fri Mar 14 16:40:53 2014 +0000
@@ -19,7 +19,7 @@
     QTemporaryDir serverConfigDir;
 
 public Q_SLOTS:
-    void downloaderError(const QString &message, Downloader::ErrorCode error);
+    void downloaderError(const QString &message, SSLConnection::ErrorCode error);
 
 private Q_SLOTS:
     void initTestCase();

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