# HG changeset patch # User Andre Heinecke # 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 -#include -#include - -#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 #include -#include -#include -#include -#include -#include -#include - 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 +#include +#include + +#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 +#include +#include +#include +#include +#include + +/** + * @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 + +#include + +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()