Mercurial > trustbridge
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();