changeset 27:62cd56cea09b

Start on polarssl Downloader. This breaks the windows build for now
author Andre Heinecke <andre.heinecke@intevation.de>
date Wed, 12 Mar 2014 16:15:52 +0100
parents cbd57d767dfa
children e783fd99a9eb
files ui/downloader.cpp ui/downloader.h ui/downloader_linux.cpp ui/downloader_win.cpp ui/mainwindow.cpp
diffstat 5 files changed, 293 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- a/ui/downloader.cpp	Tue Mar 11 19:00:35 2014 +0100
+++ b/ui/downloader.cpp	Wed Mar 12 16:15:52 2014 +0100
@@ -4,13 +4,30 @@
 #define DOWNLOAD_SERVER "https://www.intevation.de"
 #endif
 
-#ifdef Q_OS_WIN
-#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,
@@ -18,15 +35,89 @@
                        const QDateTime& newestList):
     QThread(parent),
     mUrl(url),
+    mPinnedCert(certificate),
     mLastModSW(newestSW),
-    mLastModList(newestList)
+    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);
-        mCert = certResource.readAll();
+        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()
@@ -49,3 +140,120 @@
     }
     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;
+    }
+}
--- a/ui/downloader.h	Tue Mar 11 19:00:35 2014 +0100
+++ b/ui/downloader.h	Wed Mar 12 16:15:52 2014 +0100
@@ -10,12 +10,12 @@
 #include <QString>
 #include <QByteArray>
 #include <QDateTime>
+#include <QUrl>
 
-#ifdef Q_OS_WIN
-#include <windows.h>
-#include <winhttp.h>
-#endif
-
+#include <polarssl/x509_crt.h>
+#include <polarssl/entropy.h>
+#include <polarssl/ctr_drbg.h>
+#include <polarssl/ssl.h>
 
 class Downloader: public QThread
 {
@@ -44,10 +44,13 @@
                const QDateTime& newestSW = QDateTime(),
                const QDateTime& newestList = QDateTime());
 
+    ~Downloader();
 
     enum ErrorCode {
+        NoError,
         NoConnection,
         InvalidCertificate,
+        InvalidPinnedCertificate,
         ConnectionLost,
         Timeout,
         ErrUnknown
@@ -63,56 +66,52 @@
      **/
     QString getDataDirectory();
 
+    /**
+     * @brief get the current error state
+     *
+     * @returns The current error state.
+     **/
+    ErrorCode getErrorState();
 
 protected:
     void run();
 
 private:
-    QString mUrl;
-    QByteArray mCert;
+    QUrl mUrl;
+    QByteArray mPinnedCert;
+    x509_crt mX509PinnedCert;
+    entropy_context mEntropy;
+    ctr_drbg_context mCtr_drbg;
+    ssl_context mSSL;
 
     QDateTime mLastModSW;
     QDateTime mLastModList;
 
-#ifdef Q_OS_WIN
-    /** @brief Download a file from the Internet
-     *
-     * @param[in] HSession the session to work in.
-     * @param[in] HConnect the connection to use.
-     * @param[in] resource the resource to download.
-     * @param[in] filename where the file should be saved.
-     * @param[in] maxSize maximum amount of bytes to download
-     *
-     * @returns True if the download was successful.
-     */
-    bool downloadFile(HINTERNET hSession, HINTERNET hConnect,
-            LPCWSTR resource, const QString &filename, DWORD maxSize);
+    /* Convienience to avoid having to parse all
+     * PolarSSL errors */
+    ErrorCode mErrorState;
 
-    /** @brief get the last modified header of a resource.
+    bool mInitialized;
+
+    int mServerFD;
+
+
+    /* @brief: Initialize polarssl structures
      *
-     * On error call getLastError to get extended error information.
-     * This function still does not do any networking but only initializes
-     * it.
+     * This wraps polarssl initialization functions
+     * that can return an error.
+     * Sets the error state accordingly.
      *
-     * @param[in] HSession the session to work in.
-     * @param[in] HConnect the connection to use.
-     * @param[in] resource the resource to check the last-modified date on
-     *
-     * @returns the last modified date or a null datetime in case of errors
+     * @returns: 0 on success a polarssl error otherwise.
      */
-    QDateTime getLastModifiedHeader(HINTERNET hSession,
-        HINTERNET hConnect, LPCWSTR resource);
+    int init();
 
-    /** @brief verify that the certificate of the request matches
+    /** @brief: Establish the connection
      *
-     * Validates the certificate against the member variable certificate
-     *
-     * @param[in] hRequest: The request from which to get the certificate
-     *
-     * @returns True if the certificate exactly matches the one in hRequest
+     * @returns 0 on success otherwise a polarssl error or -1 is returned
      */
-
-    bool verifyCertificate(HINTERNET hRequest);
+    int establishSSLConnection();
+#ifdef Q_OS_WIN
 #endif
 
 Q_SIGNALS:
@@ -142,6 +141,6 @@
      * @param[out] message: A message to show. Can be empty.
      * @param[out] errorCode: ErrorCode of this error.
      */
-    void error(const QString &message, ErrorCode error);
+    void error(const QString &message, Downloader::ErrorCode error);
 };
 #endif
--- a/ui/downloader_linux.cpp	Tue Mar 11 19:00:35 2014 +0100
+++ b/ui/downloader_linux.cpp	Wed Mar 12 16:15:52 2014 +0100
@@ -7,7 +7,5 @@
 #include "downloader.h"
 #ifdef Q_OS_LINUX
 
-void Downloader::run() {
-}
 #endif
 
--- a/ui/downloader_win.cpp	Tue Mar 11 19:00:35 2014 +0100
+++ b/ui/downloader_win.cpp	Wed Mar 12 16:15:52 2014 +0100
@@ -27,6 +27,44 @@
 
 #define MAX_SW_SIZE 10485760
 #define MAX_LIST_SIZE 1048576
+/** @brief Download a file from the Internet
+ *
+ * @param[in] HSession the session to work in.
+ * @param[in] HConnect the connection to use.
+ * @param[in] resource the resource to download.
+ * @param[in] filename where the file should be saved.
+ * @param[in] maxSize maximum amount of bytes to download
+ *
+ * @returns True if the download was successful.
+ */
+bool downloadFile(HINTERNET hSession, HINTERNET hConnect,
+        LPCWSTR resource, const QString &filename, DWORD maxSize);
+
+/** @brief get the last modified header of a resource.
+ *
+ * On error call getLastError to get extended error information.
+ * This function still does not do any networking but only initializes
+ * it.
+ *
+ * @param[in] HSession the session to work in.
+ * @param[in] HConnect the connection to use.
+ * @param[in] resource the resource to check the last-modified date on
+ *
+ * @returns the last modified date or a null datetime in case of errors
+ */
+QDateTime getLastModifiedHeader(HINTERNET hSession,
+    HINTERNET hConnect, LPCWSTR resource);
+
+/** @brief verify that the certificate of the request matches
+ *
+ * Validates the certificate against the member variable certificate
+ *
+ * @param[in] hRequest: The request from which to get the certificate
+ *
+ * @returns True if the certificate exactly matches the one in hRequest
+ */
+
+bool verifyCertificate(HINTERNET hRequest);
 
 
 #define LIST_RESOURCE "/incoming/aheinecke/test"
--- a/ui/mainwindow.cpp	Tue Mar 11 19:00:35 2014 +0100
+++ b/ui/mainwindow.cpp	Wed Mar 12 16:15:52 2014 +0100
@@ -20,6 +20,7 @@
 MainWindow::MainWindow() {
     createActions();
     createTrayIcon();
+    qRegisterMetaType<Downloader::ErrorCode>("Downloader::ErrorCode");
 
     connect(mTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
             this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
@@ -123,7 +124,7 @@
     QDateTime listInstalledLastMod = mSettings.value("List/installedDate").toDateTime();
     QDateTime swInstalledLastMod = mSettings.value("Software/installedDate").toDateTime();
 
-    Downloader* downloader = new Downloader(this, QString::fromLatin1("www.files.kolab.org"),
+    Downloader* downloader = new Downloader(this, QString::fromLatin1("https://files.kolab.org"),
             QByteArray(), swInstalledLastMod, listInstalledLastMod);
 
     connect(downloader, SIGNAL(newListAvailable(const QString&, const QDateTime&)),
@@ -139,8 +140,8 @@
 
 void MainWindow::downloaderError(const QString &message, Downloader::ErrorCode error)
 {
-    // TODO decide what to show when and how.
     mCurMessage = message;
+    showMessage();
 }
 
 

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