[PATCH 1 of 2] Curl based implementation of sslconnection

Wald Commits scm-commit at wald.intevation.org
Thu Aug 14 11:25:17 CEST 2014


# HG changeset patch
# User Andre Heinecke <andre.heinecke at intevation.de>
# Date 1407951308 -7200
# Node ID d1c951b3012d44b177f913b7144cd8ae33830e0c
# Parent  7bd75417e14e07f34b83e1059e42ea4c4c45692b
Curl based implementation of sslconnection

diff -r 7bd75417e14e -r d1c951b3012d INSTALL
--- a/INSTALL	Thu Aug 14 08:19:30 2014 +0200
+++ b/INSTALL	Wed Aug 13 19:35:08 2014 +0200
@@ -61,6 +61,21 @@
     cmake .. -DCMAKE_C_FLAGS=-fpic -DCMAKE_INSTALL_PREFIX=$YOURPREFIX
     make && make test && make install
 
+Libcurl:
+    wget http://curl.haxx.se/download/curl-7.37.1.tar.{gz,gz.asc}
+    gpg2 --verify curl-7.37.1.tar.gz.asc
+    tar -xf curl-7.37.1.tar.gz
+    cd curl-7.37.1/
+
+    ./configure --prefix=$YOURPREFIX \
+    --without-nghttp2 --without-libidn --without-winidn --without-libssh2 \
+    --without-librtmp --without-libmetalink --without-axtls --without-nss \
+    --without-cyassl --without-ssl  --without-gnutls --disable-gopher --disable-smtp \
+    --disable-imap --disable-pop3 --disable-tftp --disable-telnet --disable-dict \
+    --disable-proxy --disable-rtsp --disable-ldaps --disable-ldap --disable-file \
+    --disable-ftp --enable-http --enable-shared=no -enable-static=yes \
+    --with-polarssl=$YOURPREFIX --without-ca-bundle --without-ca-path
+
 To compile the software you can use plain cmake. An out of source build is
 highly suggested. For build options see CMakeList.txt
 
diff -r 7bd75417e14e -r d1c951b3012d ui/CMakeLists.txt
--- a/ui/CMakeLists.txt	Thu Aug 14 08:19:30 2014 +0200
+++ b/ui/CMakeLists.txt	Wed Aug 13 19:35:08 2014 +0200
@@ -12,6 +12,9 @@
 add_definitions(${Qt5Widgets_DEFINITIONS})
 
 find_package(Qt5LinguistTools)
+find_package(CURL)
+
+include_directories(${CURL_INCLUDE_DIRS})
 
 # Common code is used in either the client or the administrator
 # application or in unit tests.
@@ -19,10 +22,17 @@
     ${CMAKE_CURRENT_SOURCE_DIR}/certificatelist.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/certificate.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/downloader.cpp
-    ${CMAKE_CURRENT_SOURCE_DIR}/sslconnection.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/sslconnection_bare.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/sslhelp.cpp
 )
 
+if (${CURL_FOUND})
+   set(UICOMMON_SOURCES ${UICOMMON_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/sslconnection_curl.cpp)
+   add_definitions(-DUSE_CURL)
+else()
+   MESSAGE(STATUS "Warning curl not found only bare polarssl ssl will be supported.")
+endif()
+
 # Cmake does not correctly identify gcc windres when cross compiling
 # making this line neccessary to set the correct flags for it.
 # See: http://public.kitware.com/Bug/view.php?id=11773
@@ -194,6 +204,8 @@
    Qt5::Widgets
    ui_common
    trustbridge_common
+   ${CURL_LIBRARIES}
+   z
    ${POLARSSL_LIBRARIES}
    ${EXTRA_STATIC_LIBS}
    ${PROFILING_LIBS})
diff -r 7bd75417e14e -r d1c951b3012d ui/downloader.cpp
--- a/ui/downloader.cpp	Thu Aug 14 08:19:30 2014 +0200
+++ b/ui/downloader.cpp	Wed Aug 13 19:35:08 2014 +0200
@@ -28,6 +28,8 @@
 #define MAX_SW_SIZE 15728640
 #define MAX_LIST_SIZE 1048576
 
+#include "sslconnection_curl.h"
+#include "sslconnection_bare.h"
 
 Downloader::Downloader(QObject* parent, const QString& url,
                        const QByteArray& certificate,
@@ -41,13 +43,18 @@
     mLastModList(newestList),
     mResourceSW(resourceSW),
     mResourceList(resourceList),
-    mDownloadSW(downloadSW),
-    mSSLConnection(url, certificate)
+    mDownloadSW(downloadSW)
 {
+#ifdef USE_CURL
+    mSSLConnection = new SSLConnectionCurl(url, certificate);
+#else
+    mSSLConnection = new SSLConnectionBare(url, certificate);
+#endif
 }
 
 
 Downloader::~Downloader() {
+    delete mSSLConnection;
 }
 
 QString Downloader::getDataDirectory()
@@ -112,13 +119,13 @@
     QString headRequest =
         QString::fromLatin1("HEAD %1 HTTP/1.0\r\n\r\n").arg(resource);
 
-    ret = mSSLConnection.write(headRequest.toUtf8());
+    ret = mSSLConnection->write(headRequest.toUtf8());
     if (ret != 0) {
         emit error (tr("Connection lost"), SSLConnection::ConnectionLost);
         return QDateTime();
     }
 
-    response = mSSLConnection.read(1024);
+    response = mSSLConnection->read(1024);
 
     qDebug() << "Response from server was: " << response;
 
@@ -152,7 +159,7 @@
 
     QSaveFile outputFile(fileName);
 
-    ret = mSSLConnection.write(getRequest.toUtf8());
+    ret = mSSLConnection->write(getRequest.toUtf8());
 
     // Open / Create the file to write to.
     if (!outputFile.open(QIODevice::WriteOnly)) {
@@ -170,7 +177,7 @@
     do {
         /* Read the response in 8KiB chunks */
         int responseSize = 0;
-        QByteArray response = mSSLConnection.read(8192);
+        QByteArray response = mSSLConnection->read(8192);
         if (response.isNull()) {
             qDebug() << "Error reading response";
             emit error(tr("Connection lost"), SSLConnection::ConnectionLost);
@@ -198,16 +205,16 @@
     QDateTime remoteModList;
     QDateTime remoteModSW;
 
-    if (!mSSLConnection.initialized()) {
+    if (!mSSLConnection->initialized()) {
         emit error(tr("Failed to initialize SSL Module."), SSLConnection::ErrUnknown);
         return;
     }
 
-    ret = mSSLConnection.connect();
+    ret = mSSLConnection->connect();
 
     if (ret != 0) {
         emit error(tr("Failed to connect."),
-                   mSSLConnection.getLastError());
+                   mSSLConnection->getLastError());
         return;
     }
 
diff -r 7bd75417e14e -r d1c951b3012d ui/downloader.h
--- a/ui/downloader.h	Thu Aug 14 08:19:30 2014 +0200
+++ b/ui/downloader.h	Wed Aug 13 19:35:08 2014 +0200
@@ -83,7 +83,7 @@
 
     bool mDownloadSW;
 
-    SSLConnection mSSLConnection;
+    SSLConnection *mSSLConnection;
 
     /** @brief get the last modified header of a resource.
      *
diff -r 7bd75417e14e -r d1c951b3012d ui/sslconnection.cpp
--- a/ui/sslconnection.cpp	Thu Aug 14 08:19:30 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,367 +0,0 @@
-/* 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.
- */
-/* TODO: Wrap ssl_session in a class for reuse.
- * see programs/ssl/ssl_client2.c for example of session reuse */
-#include "sslconnection.h"
-#include "sslhelp.h"
-
-#include <QFile>
-#include <QUuid>
-#include <QApplication>
-
-#define MAX_IO_TRIES 10
-#define MAX_RESETS 10
-
-#ifdef CONNECTION_DEBUG
-static void my_debug(void *ctx, int level, const char *str)
-{
-    fprintf((FILE *) ctx, "%s", str);
-    fflush((FILE *) ctx);
-}
-#endif
-
-SSLConnection::SSLConnection(const QString& url,
-                             const QByteArray& certificate):
-    mUrl(url),
-    mPinnedCert(certificate),
-    mInitialized(false),
-    mConnected(false),
-    mNeedsReset(false),
-    mServerFD(-1),
-    mErrorState(NoError)
-{
-    int ret = -1;
-
-    memset(&mSSL, 0, sizeof(ssl_context));
-    memset(&mSavedSession, 0, sizeof( ssl_session ) );
-
-    if (certificate.isEmpty()) {
-        QFile certResource(":certs/intevation.de");
-        certResource.open(QFile::ReadOnly);
-        mPinnedCert = certResource.readAll();
-        certResource.close();
-    }
-
-    ret = init();
-    if (ret == 0) {
-        mInitialized = true;
-    } else {
-        qDebug() << "Initialization error: " + getPolarSSLErrorMsg(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() {
-    disconnect();
-    x509_crt_free(&mX509PinnedCert);
-    entropy_free(&mEntropy);
-    if (mInitialized) {
-        ssl_free(&mSSL);
-    }
-}
-
-void SSLConnection::disconnect() {
-    if (mConnected) {
-        ssl_close_notify(&mSSL);
-        if (mServerFD != -1) {
-            net_close(mServerFD);
-            mServerFD = -1;
-        }
-        ssl_session_free(&mSavedSession);
-        mConnected = false;
-    }
-}
-
-int SSLConnection::connect() {
-    int ret = -1;
-
-    if (!mInitialized) {
-        mErrorState = ErrUnknown;
-        return -1;
-    }
-
-    ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
-                  mUrl.port(443));
-
-    if (ret != 0) {
-        qDebug() << "Connect failed: " << getPolarSSLErrorMsg(ret);
-        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: " << getPolarSSLErrorMsg(ret);
-            mErrorState = SSLHandshakeFailed;
-            return ret;
-        }
-    }
-
-    ret = ssl_get_session(&mSSL, &mSavedSession);
-    if (ret != 0) {
-        qDebug() << "SSL get session failed: " << getPolarSSLErrorMsg(ret);
-
-        mErrorState = NoConnection;
-        return ret;
-    }
-    printf( " ok\n    [ Ciphersuite is %s ]\n",
-            ssl_get_ciphersuite( &mSSL) );
-    ret = validateCertificate();
-
-    if (ret == 0) {
-        mConnected = true;
-    }
-    return ret;
-}
-
-int SSLConnection::validateCertificate()
-{
-    int ret = -1;
-    const x509_crt *peerCert = NULL;
-
-    /* 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();
-
-    if (mNeedsReset) {
-        ret = reset();
-        if (ret != 0) {
-            qDebug() << "Reset failed: " << getPolarSSLErrorMsg(ret);
-            return ret;
-        }
-    }
-
-    qDebug() << "Sending 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 &&
-                ret != POLARSSL_ERR_NET_WANT_READ) {
-            return ret;
-        }
-        tries++;
-        net_usleep(100000); /* sleep 100ms to give the socket a chance
-                               to clean up. */
-    } while (tries < MAX_IO_TRIES);
-
-    return ret;
-}
-
-
-int SSLConnection::reset()
-{
-    int ret = -1;
-    ssl_close_notify(&mSSL);
-
-    ret = ssl_session_reset(&mSSL);
-    if (ret != 0)
-    {
-        qDebug() << "SSL Connection reset failed: "
-                 << getPolarSSLErrorMsg(ret);
-        return ret;
-    }
-
-    ssl_set_session(&mSSL, &mSavedSession);
-
-    ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
-                  mUrl.port(443));
-
-    if (ret != 0) {
-        mErrorState = NoConnection;
-        qDebug() << "Connection failed." << getPolarSSLErrorMsg(ret);
-        return ret;
-    }
-
-    while ((ret = ssl_handshake(&mSSL)) != 0) {
-        if (ret != POLARSSL_ERR_NET_WANT_READ &&
-                ret != POLARSSL_ERR_NET_WANT_WRITE) {
-            qDebug() << "SSL Handshake failed: "
-                 << getPolarSSLErrorMsg(ret);
-            mErrorState = SSLHandshakeFailed;
-            return ret;
-        }
-    }
-
-    qDebug() << "Reset connection. ";
-    /* Validation should not be necessary as we reused a saved
-     * session. But just to be sure. */
-    return validateCertificate();
-}
-
-QByteArray SSLConnection::read(size_t len)
-{
-    unsigned char buf[len];
-    QByteArray retval("");
-    int ret = -1;
-    unsigned int tries = 0;
-
-    mNeedsReset = true;
-    do {
-        memset (buf, 0, sizeof(buf));
-        ret = ssl_read(&mSSL, buf, len);
-        if (ret == 0 ||
-            ret == POLARSSL_ERR_SSL_CONN_EOF ||
-            ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY) {
-            /* EOF */
-            return retval;
-        }
-        if (ret == POLARSSL_ERR_NET_WANT_WRITE ||
-                ret == POLARSSL_ERR_NET_WANT_READ) {
-            net_usleep(100000); /* sleep 100ms to give the socket a chance
-                                   to recover */
-            tries++;
-        }
-        if (ret <= 0) {
-            qDebug() << "Read failed: " << getPolarSSLErrorMsg(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, ret);
-    } while (len > 0 && tries < MAX_IO_TRIES);
-
-    return retval;
-}
-
diff -r 7bd75417e14e -r d1c951b3012d ui/sslconnection.h
--- a/ui/sslconnection.h	Thu Aug 14 08:19:30 2014 +0200
+++ b/ui/sslconnection.h	Wed Aug 13 19:35:08 2014 +0200
@@ -18,13 +18,6 @@
 #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:
@@ -47,12 +40,19 @@
      * @param[in] certificate optional certificate to validate https connection
      */
     SSLConnection(const QString& url,
-                  const QByteArray& certificate = QByteArray());
+                  const QByteArray& certificate = QByteArray()) :
+        mUrl(url),
+        mPinnedCert(certificate),
+        mInitialized(false),
+        mConnected(false),
+        mNeedsReset(false),
+        mServerFD(-1),
+        mErrorState(NoError) {};
 
-    ~SSLConnection();
+    virtual ~SSLConnection() {};
 
     /** @brief write */
-    int write(const QByteArray& request);
+    virtual int write(const QByteArray& request) = 0;
 
     /**
      * @brief read at most len bytes and reset the connection
@@ -61,7 +61,7 @@
      *
      * @returns a byte array containing the data or
      * a NULL byte array on error*/
-    QByteArray read(size_t len);
+    virtual QByteArray read(size_t len) = 0;
 
     bool initialized() { return mInitialized; }
     bool connected() { return mConnected; }
@@ -70,51 +70,19 @@
 
     /** @brief: Establish the connection
      *
-     * @returns 0 on success otherwise a polarssl error or -1 is returned
+     * @returns 0 on success otherwise an error or -1 is returned
      */
-    int connect();
+    virtual int connect() = 0;
 
-private:
+protected:
     QUrl mUrl;
     QByteArray mPinnedCert;
-    x509_crt mX509PinnedCert;
-    entropy_context mEntropy;
-    ctr_drbg_context mCtr_drbg;
-    ssl_context mSSL;
-    ssl_session mSavedSession;
     bool mInitialized;
     bool mConnected; /* A connection was established */
     bool mNeedsReset; /* The connection needs to be reset before the next
                          write */
     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();
-
-    /* @brief Reset the connection.
-     *
-     * Resets the https connection and does another handshake.
-     *
-     * @returns: 0 on success a polarssl error or -1 otherwise. */
-    int reset();
-
-    /* @brief validates that the certificate matches the pinned one.
-     *
-     * Checks the peer certificate of mSSL and validates that the
-     * certificate matches mPinnedCertificate.
-     *
-     * @returns: 0 on success a polarssl error or -1 otherwise. */
-    int validateCertificate();
-
-    /* @brief disconnects the connection */
-    void disconnect();
 };
 
 #endif
diff -r 7bd75417e14e -r d1c951b3012d ui/sslconnection_bare.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/sslconnection_bare.cpp	Wed Aug 13 19:35:08 2014 +0200
@@ -0,0 +1,362 @@
+/* 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.
+ */
+/* TODO: Wrap ssl_session in a class for reuse.
+ * see programs/ssl/ssl_client2.c for example of session reuse */
+#include "sslconnection_bare.h"
+#include "sslhelp.h"
+
+#include <QFile>
+#include <QUuid>
+#include <QApplication>
+
+#define MAX_IO_TRIES 10
+#define MAX_RESETS 10
+
+#ifdef CONNECTION_DEBUG
+static void my_debug(void *ctx, int level, const char *str)
+{
+    fprintf((FILE *) ctx, "%s", str);
+    fflush((FILE *) ctx);
+}
+#endif
+
+SSLConnectionBare::SSLConnectionBare(const QString& url,
+                             const QByteArray& certificate):
+    SSLConnection (url, certificate)
+{
+    int ret = -1;
+
+    memset(&mSSL, 0, sizeof(ssl_context));
+    memset(&mSavedSession, 0, sizeof( ssl_session ) );
+
+    if (certificate.isEmpty()) {
+        QFile certResource(":certs/intevation.de");
+        certResource.open(QFile::ReadOnly);
+        mPinnedCert = certResource.readAll();
+        certResource.close();
+    }
+
+    ret = init();
+    if (ret == 0) {
+        mInitialized = true;
+    } else {
+        qDebug() << "Initialization error: " + getPolarSSLErrorMsg(ret);
+    }
+}
+
+int SSLConnectionBare::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;
+}
+
+SSLConnectionBare::~SSLConnectionBare() {
+    disconnect();
+    x509_crt_free(&mX509PinnedCert);
+    entropy_free(&mEntropy);
+    if (mInitialized) {
+        ssl_free(&mSSL);
+    }
+}
+
+void SSLConnectionBare::disconnect() {
+    if (mConnected) {
+        ssl_close_notify(&mSSL);
+        if (mServerFD != -1) {
+            net_close(mServerFD);
+            mServerFD = -1;
+        }
+        ssl_session_free(&mSavedSession);
+        mConnected = false;
+    }
+}
+
+int SSLConnectionBare::connect() {
+    int ret = -1;
+
+    if (!mInitialized) {
+        mErrorState = ErrUnknown;
+        return -1;
+    }
+
+    ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
+                  mUrl.port(443));
+
+    if (ret != 0) {
+        qDebug() << "Connect failed: " << getPolarSSLErrorMsg(ret);
+        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: " << getPolarSSLErrorMsg(ret);
+            mErrorState = SSLHandshakeFailed;
+            return ret;
+        }
+    }
+
+    ret = ssl_get_session(&mSSL, &mSavedSession);
+    if (ret != 0) {
+        qDebug() << "SSL get session failed: " << getPolarSSLErrorMsg(ret);
+
+        mErrorState = NoConnection;
+        return ret;
+    }
+    printf( " ok\n    [ Ciphersuite is %s ]\n",
+            ssl_get_ciphersuite( &mSSL) );
+    ret = validateCertificate();
+
+    if (ret == 0) {
+        mConnected = true;
+    }
+    return ret;
+}
+
+int SSLConnectionBare::validateCertificate()
+{
+    int ret = -1;
+    const x509_crt *peerCert = NULL;
+
+    /* 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 SSLConnectionBare::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();
+
+    if (mNeedsReset) {
+        ret = reset();
+        if (ret != 0) {
+            qDebug() << "Reset failed: " << getPolarSSLErrorMsg(ret);
+            return ret;
+        }
+    }
+
+    qDebug() << "Sending 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 &&
+                ret != POLARSSL_ERR_NET_WANT_READ) {
+            return ret;
+        }
+        tries++;
+        net_usleep(100000); /* sleep 100ms to give the socket a chance
+                               to clean up. */
+    } while (tries < MAX_IO_TRIES);
+
+    return ret;
+}
+
+
+int SSLConnectionBare::reset()
+{
+    int ret = -1;
+    ssl_close_notify(&mSSL);
+
+    ret = ssl_session_reset(&mSSL);
+    if (ret != 0)
+    {
+        qDebug() << "SSL Connection reset failed: "
+                 << getPolarSSLErrorMsg(ret);
+        return ret;
+    }
+
+    ssl_set_session(&mSSL, &mSavedSession);
+
+    ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
+                  mUrl.port(443));
+
+    if (ret != 0) {
+        mErrorState = NoConnection;
+        qDebug() << "Connection failed." << getPolarSSLErrorMsg(ret);
+        return ret;
+    }
+
+    while ((ret = ssl_handshake(&mSSL)) != 0) {
+        if (ret != POLARSSL_ERR_NET_WANT_READ &&
+                ret != POLARSSL_ERR_NET_WANT_WRITE) {
+            qDebug() << "SSL Handshake failed: "
+                 << getPolarSSLErrorMsg(ret);
+            mErrorState = SSLHandshakeFailed;
+            return ret;
+        }
+    }
+
+    qDebug() << "Reset connection. ";
+    /* Validation should not be necessary as we reused a saved
+     * session. But just to be sure. */
+    return validateCertificate();
+}
+
+QByteArray SSLConnectionBare::read(size_t len)
+{
+    unsigned char buf[len];
+    QByteArray retval("");
+    int ret = -1;
+    unsigned int tries = 0;
+
+    mNeedsReset = true;
+    do {
+        memset (buf, 0, sizeof(buf));
+        ret = ssl_read(&mSSL, buf, len);
+        if (ret == 0 ||
+            ret == POLARSSL_ERR_SSL_CONN_EOF ||
+            ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY) {
+            /* EOF */
+            return retval;
+        }
+        if (ret == POLARSSL_ERR_NET_WANT_WRITE ||
+                ret == POLARSSL_ERR_NET_WANT_READ) {
+            net_usleep(100000); /* sleep 100ms to give the socket a chance
+                                   to recover */
+            tries++;
+        }
+        if (ret <= 0) {
+            qDebug() << "Read failed: " << getPolarSSLErrorMsg(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, ret);
+    } while (len > 0 && tries < MAX_IO_TRIES);
+
+    return retval;
+}
+
+
diff -r 7bd75417e14e -r d1c951b3012d ui/sslconnection_bare.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/sslconnection_bare.h	Wed Aug 13 19:35:08 2014 +0200
@@ -0,0 +1,87 @@
+#ifndef UI_SSLCONNECTION_BARE_H
+#define UI_SSLCONNECTION_BARE_H
+/* 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 "sslconnection.h"
+
+#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>
+
+/**
+ * @file sslconnection_bare.h
+ * @brief SSLConnection doing bare SSL over PolarSSL
+ * */
+
+class SSLConnectionBare : public SSLConnection
+{
+public:
+    SSLConnectionBare(const QString& url,
+            const QByteArray& certificate = QByteArray());
+
+    ~SSLConnectionBare();
+
+    /** @brief write */
+    int write(const QByteArray& request);
+
+    /**
+     * @brief read at most len bytes and reset the connection
+     *
+     * @param [in] len Amount of bytes to read.
+     *
+     * @returns a byte array containing the data or
+     * a NULL byte array on error*/
+    QByteArray read(size_t len);
+
+    /** @brief: Establish the connection
+     *
+     * @returns 0 on success otherwise an error or -1 is returned
+     */
+    int connect();
+
+private:
+    x509_crt mX509PinnedCert;
+    entropy_context mEntropy;
+    ctr_drbg_context mCtr_drbg;
+    ssl_context mSSL;
+    ssl_session mSavedSession;
+
+    /* @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 Reset the connection.
+     *
+     * Resets the https connection and does another handshake.
+     *
+     * @returns: 0 on success a polarssl error or -1 otherwise. */
+    int reset();
+
+    /* @brief validates that the certificate matches the pinned one.
+     *
+     * Checks the peer certificate of mSSL and validates that the
+     * certificate matches mPinnedCertificate.
+     *
+     * @returns: 0 on success a polarssl error or -1 otherwise. */
+    int validateCertificate();
+
+    /* @brief disconnects the connection */
+    void disconnect();
+};
+
+#endif // UI_SSLCONNECTION_BARE_H
diff -r 7bd75417e14e -r d1c951b3012d ui/sslconnection_curl.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/sslconnection_curl.cpp	Wed Aug 13 19:35:08 2014 +0200
@@ -0,0 +1,133 @@
+/* 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 "sslconnection_curl.h"
+
+#define CONNECTION_DEBUG
+
+SSLConnectionCurl::SSLConnectionCurl(const QString& url,
+                             const QByteArray& certificate):
+    SSLConnection (url, certificate),
+    mCurl (NULL)
+{
+    curl_global_init(CURL_GLOBAL_DEFAULT);
+    mCurl = curl_easy_init();
+
+    if (!mCurl) {
+        qDebug() << "Failed to initialize curl";
+        return;
+    }
+
+    if (curl_easy_setopt(mCurl, CURLOPT_URL, QUrl(url).toEncoded().constData()) != CURLE_OK) {
+        qDebug() << "Setting url failed";
+        return;
+    }
+
+    if (curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYPEER, 1L) != CURLE_OK) {
+        /* Should be default anyway */
+        qDebug() << "Setting verifypeer failed";
+        return;
+    }
+
+    if (curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYHOST, 0L) != CURLE_OK) {
+        /* There are no limitiations for the pinned certificate */
+        qDebug() << "Setting verifyhost failed";
+        return;
+    }
+
+    mCertFile.open();
+    if (mCertFile.write(mPinnedCert) != mPinnedCert.size()) {
+        qDebug() << "Failed to write temporary certificate";
+        return;
+    }
+    mCertFile.close();
+
+    if (curl_easy_setopt(mCurl, CURLOPT_CAINFO,
+                mCertFile.fileName().toUtf8().constData()) != CURLE_OK) {
+        qDebug() << "Failed to write temporary certificate";
+        return;
+    }
+    mInitialized = true;
+
+#ifdef CONNECTION_DEBUG
+    curl_easy_setopt(mCurl, CURLOPT_VERBOSE, 1L);
+#endif
+}
+
+SSLConnectionCurl::~SSLConnectionCurl() {
+    if (mCurl) {
+        curl_easy_cleanup (mCurl);
+    }
+    if (mInitialized) {
+        mCertFile.close();
+    }
+    curl_global_cleanup();
+}
+
+int SSLConnectionCurl::connect() {
+    if (curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 1L) != CURLE_OK) {
+        qDebug() << "Failed to set connect only option";
+        return -1;
+    }
+    if (curl_easy_perform (mCurl) != CURLE_OK) {
+        qDebug() << "Failed to connect";
+        mErrorState = NoConnection;
+        return -1;
+    }
+    mConnected = true;
+    return 0;
+}
+
+int SSLConnectionCurl::write(const QByteArray& request) {
+    size_t written = 0;
+
+    if (curl_easy_send (mCurl, request.constData(), request.size(), &written) != CURLE_OK) {
+        qDebug() << "Failed to send request";
+        return -1;
+    }
+    if (written != (size_t)request.size()) {
+        qDebug() << "Failed to write everything";
+        return -1;
+    }
+    return 0;
+}
+
+QByteArray SSLConnectionCurl::read(size_t len)
+{
+    unsigned char buf[len];
+    QByteArray retval("");
+    CURLcode ret;
+    size_t read = 0;
+    unsigned int tries = 0;
+
+    do {
+        memset (buf, 0, sizeof(buf));
+        ret = curl_easy_recv (mCurl, buf, len, &read);
+        if (ret == CURLE_OK && read == 0) {
+            return retval;
+        }
+        if (ret == CURLE_AGAIN) {
+            tries++;
+            continue;
+        }
+        if (ret != CURLE_OK) {
+            qDebug() << "Read failed.";
+            return QByteArray();
+        }
+        if (len < (len - read)) {
+            /* Should never happen if ssl_read behaves */
+            qDebug() << "integer overflow in polarSSLRead";
+            return QByteArray();
+        }
+
+        len -= read;
+        retval.append((const char *)buf, read);
+    } while (len > 0 && tries < 10);
+
+    return retval;
+}
diff -r 7bd75417e14e -r d1c951b3012d ui/sslconnection_curl.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/sslconnection_curl.h	Wed Aug 13 19:35:08 2014 +0200
@@ -0,0 +1,50 @@
+#ifndef UI_SSLCONNECTION_CURL_H
+#define UI_SSLCONNECTION_CURL_H
+/* 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 "sslconnection.h"
+#include <curl/curl.h>
+
+#include <QTemporaryFile>
+
+class SSLConnectionCurl : public SSLConnection
+{
+public:
+    SSLConnectionCurl(const QString& url,
+            const QByteArray& certificate = QByteArray());
+
+    ~SSLConnectionCurl();
+
+    /** @brief write */
+    int write(const QByteArray& request);
+
+    /**
+     * @brief read at most len bytes and reset the connection
+     *
+     * @param [in] len Amount of bytes to read.
+     *
+     * @returns a byte array containing the data or
+     * a NULL byte array on error*/
+    QByteArray read(size_t len);
+
+    /** @brief: Establish the connection
+     *
+     * @returns 0 on success otherwise an error or -1 is returned
+     */
+    int connect();
+private:
+    CURL *mCurl;
+    QTemporaryFile mCertFile;
+};
+
+/**
+ * @file sslconnection_curl.h
+ * @brief SSLConnection utilizing libcurl for http.
+ */
+#endif // UI_SSLCONNECTION_CURL_H
diff -r 7bd75417e14e -r d1c951b3012d ui/tests/CMakeLists.txt
--- a/ui/tests/CMakeLists.txt	Thu Aug 14 08:19:30 2014 +0200
+++ b/ui/tests/CMakeLists.txt	Wed Aug 13 19:35:08 2014 +0200
@@ -47,6 +47,8 @@
      ui_common
      Qt5::Test Qt5::Widgets
      trustbridge_common
+     ${CURL_LIBRARIES}
+     z
      ${POLARSSL_LIBRARIES}
      ${EXTRA_STATIC_LIBS})
 endmacro()


More information about the Trustbridge-commits mailing list