# HG changeset patch # User Andre Heinecke # Date 1394637352 -3600 # Node ID 62cd56cea09bef52f8282b42cbcc84dfa1002d68 # Parent cbd57d767dfa30f50ad7621b26d969d7d88331b6 Start on polarssl Downloader. This breaks the windows build for now diff -r cbd57d767dfa -r 62cd56cea09b ui/downloader.cpp --- a/ui/downloader.cpp Tue Mar 11 19:00:35 2014 +0100 +++ b/ui/downloader.cpp Wed Mar 12 16:15:52 2014 +0100 @@ -4,13 +4,30 @@ #define DOWNLOAD_SERVER "https://www.intevation.de" #endif -#ifdef Q_OS_WIN -#endif - #include #include #include #include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define MAX_SW_SIZE 10485760 +#define MAX_LIST_SIZE 1048576 + +QString getErrorMsg(int ret) +{ + char errbuf[255]; + polarssl_strerror(ret, errbuf, 255); + errbuf[254] = '\0'; /* Just to be sure */ + return QString::fromLatin1(errbuf); +} Downloader::Downloader(QObject* parent, const QString& url, const QByteArray& certificate, @@ -18,15 +35,89 @@ const QDateTime& newestList): QThread(parent), mUrl(url), + mPinnedCert(certificate), mLastModSW(newestSW), - mLastModList(newestList) + mLastModList(newestList), + mErrorState(NoError), + mInitialized(false), + mServerFD(-1) { + int ret = -1; + + memset(&mSSL, 0, sizeof(ssl_context)); + if (certificate.isEmpty()) { QFile certResource(":certs/kolab.org"); certResource.open(QFile::ReadOnly); - mCert = certResource.readAll(); + mPinnedCert = certResource.readAll(); certResource.close(); } + + ret = init(); + if (ret == 0) { + mInitialized = true; + } else { + qDebug() << "Initialization error: " + getErrorMsg(ret); + } +} + +int Downloader::init() +{ + int ret = -1; + QUuid uuid = QUuid::createUuid(); + QString personalString = QApplication::applicationName() + uuid.toString(); + QByteArray personalBa = personalString.toLocal8Bit(); + + x509_crt_init(&mX509PinnedCert); + entropy_init(&mEntropy); + + ret = ssl_init(&mSSL); + if (ret != 0) { + /* The only documented error is malloc failed */ + mErrorState = ErrUnknown; + return ret; + } + + /* + * Initialize random generator. + * Personalisation string, does not need to be random but + * should be unique according to documentation. + * + * the ctr_drbg structure does not need to be freed explicitly. + */ + ret = ctr_drbg_init(&mCtr_drbg, entropy_func, &mEntropy, + (const unsigned char*) personalBa.constData(), + personalBa.size()); + if (ret != 0) { + ssl_free(&mSSL); + mErrorState = ErrUnknown; + return ret; + } + + ret = x509_crt_parse(&mX509PinnedCert, + (const unsigned char*) mPinnedCert.constData(), + mPinnedCert.size()); + if (ret != 0){ + ssl_free(&mSSL); + mErrorState = InvalidPinnedCertificate; + return ret; + } + + ssl_set_endpoint(&mSSL, SSL_IS_CLIENT); + ssl_set_authmode(&mSSL, SSL_VERIFY_OPTIONAL); + ssl_set_ca_chain(&mSSL, &mX509PinnedCert, NULL, NULL); + ssl_set_renegotiation(&mSSL, SSL_RENEGOTIATION_DISABLED); + ssl_set_rng(&mSSL, ctr_drbg_random, &mCtr_drbg); + + return 0; +} + +Downloader::~Downloader() { + x509_crt_free(&mX509PinnedCert); + entropy_free(&mEntropy); + if (mInitialized) { + ssl_free(&mSSL); + } } QString Downloader::getDataDirectory() @@ -49,3 +140,120 @@ } return cDir.absolutePath(); } + +int Downloader::establishSSLConnection() { + int ret = -1; + const x509_crt *peerCert; + + mErrorState = ErrUnknown; + + if (mServerFD == -1) { + return -1; + } + + ssl_set_bio(&mSSL, net_recv, &mServerFD, + net_send, &mServerFD); + + while ((ret = ssl_handshake(&mSSL)) != 0) { + if (ret != POLARSSL_ERR_NET_WANT_READ && + ret != POLARSSL_ERR_NET_WANT_WRITE) { + qDebug() << "SSL Handshake failed: " + << getErrorMsg(ret); + return ret; + } + } + + ret = ssl_get_verify_result(&mSSL); + + if (ret != 0 ) { + + if( ( ret & BADCERT_EXPIRED ) != 0 ) + qDebug() << "server certificate has expired"; + + if( ( ret & BADCERT_REVOKED ) != 0 ) + qDebug() << "server certificate has been revoked"; + + if( ( ret & BADCERT_CN_MISMATCH ) != 0 ) + qDebug() << "CN mismatch"; + + if( ( ret & BADCERT_NOT_TRUSTED ) != 0 ) + qDebug() << "self-signed or not signed by a trusted CA"; + +#ifdef RELEASE_BUILD + return -1; +#endif + } + + peerCert = ssl_get_peer_cert(&mSSL); + + if (!peerCert) { + mErrorState = InvalidCertificate; + qDebug() << "Failed to get peer cert"; + return -1; + } + + if (peerCert->raw.len == 0 || + peerCert->raw.len != mX509PinnedCert.raw.len) { + mErrorState = InvalidCertificate; + qDebug() << "Certificate length mismatch"; + return -1; + } + + /* You can never be sure what those c++ operators do.. + if (mPinnedCert != QByteArray::fromRawData( + (const char*) peerCert->raw.p, + peerCert->raw.len)) { + qDebug() << "Certificate content mismatch"; + } + */ + + for (unsigned int i = 0; i < peerCert->raw.len; i++) { + if (peerCert->raw.p[i] != mX509PinnedCert.raw.p[i]) { + qDebug() << "Certificate content mismatch"; + mErrorState = InvalidCertificate; + return -1; + } + } + mErrorState = NoError; + return 0; +} + + +void Downloader::run() { + int ret; + + if (!mInitialized) { + emit error(tr("Failed to initialize SSL Module."), ErrUnknown); + return; + } + + ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(), + mUrl.port(443)); + if (ret != 0) { + mErrorState = NoConnection; + emit error(tr("Failed to connect to %1.").arg(mUrl.host()), + mErrorState); + return; + } + + emit progress(tr("Connected"), 1, -1); + + ret = establishSSLConnection(); + if (ret != 0) { + qDebug() << "SSL conncetion failed: " << getErrorMsg(ret); + emit error(tr("Failed to connect to %1.").arg(mUrl.host()), + mErrorState); + return; + } + + qDebug() << "Connected to: " << mUrl.host(); + // TODO + + emit progress(tr("Closing"), 1, -1); + ssl_close_notify (&mSSL); + + if (mServerFD != -1) { + net_close(mServerFD); + mServerFD = -1; + } +} diff -r cbd57d767dfa -r 62cd56cea09b ui/downloader.h --- a/ui/downloader.h Tue Mar 11 19:00:35 2014 +0100 +++ b/ui/downloader.h Wed Mar 12 16:15:52 2014 +0100 @@ -10,12 +10,12 @@ #include #include #include +#include -#ifdef Q_OS_WIN -#include -#include -#endif - +#include +#include +#include +#include class Downloader: public QThread { @@ -44,10 +44,13 @@ const QDateTime& newestSW = QDateTime(), const QDateTime& newestList = QDateTime()); + ~Downloader(); enum ErrorCode { + NoError, NoConnection, InvalidCertificate, + InvalidPinnedCertificate, ConnectionLost, Timeout, ErrUnknown @@ -63,56 +66,52 @@ **/ QString getDataDirectory(); + /** + * @brief get the current error state + * + * @returns The current error state. + **/ + ErrorCode getErrorState(); protected: void run(); private: - QString mUrl; - QByteArray mCert; + QUrl mUrl; + QByteArray mPinnedCert; + x509_crt mX509PinnedCert; + entropy_context mEntropy; + ctr_drbg_context mCtr_drbg; + ssl_context mSSL; QDateTime mLastModSW; QDateTime mLastModList; -#ifdef Q_OS_WIN - /** @brief Download a file from the Internet - * - * @param[in] HSession the session to work in. - * @param[in] HConnect the connection to use. - * @param[in] resource the resource to download. - * @param[in] filename where the file should be saved. - * @param[in] maxSize maximum amount of bytes to download - * - * @returns True if the download was successful. - */ - bool downloadFile(HINTERNET hSession, HINTERNET hConnect, - LPCWSTR resource, const QString &filename, DWORD maxSize); + /* Convienience to avoid having to parse all + * PolarSSL errors */ + ErrorCode mErrorState; - /** @brief get the last modified header of a resource. + bool mInitialized; + + int mServerFD; + + + /* @brief: Initialize polarssl structures * - * On error call getLastError to get extended error information. - * This function still does not do any networking but only initializes - * it. + * This wraps polarssl initialization functions + * that can return an error. + * Sets the error state accordingly. * - * @param[in] HSession the session to work in. - * @param[in] HConnect the connection to use. - * @param[in] resource the resource to check the last-modified date on - * - * @returns the last modified date or a null datetime in case of errors + * @returns: 0 on success a polarssl error otherwise. */ - QDateTime getLastModifiedHeader(HINTERNET hSession, - HINTERNET hConnect, LPCWSTR resource); + int init(); - /** @brief verify that the certificate of the request matches + /** @brief: Establish the connection * - * Validates the certificate against the member variable certificate - * - * @param[in] hRequest: The request from which to get the certificate - * - * @returns True if the certificate exactly matches the one in hRequest + * @returns 0 on success otherwise a polarssl error or -1 is returned */ - - bool verifyCertificate(HINTERNET hRequest); + int establishSSLConnection(); +#ifdef Q_OS_WIN #endif Q_SIGNALS: @@ -142,6 +141,6 @@ * @param[out] message: A message to show. Can be empty. * @param[out] errorCode: ErrorCode of this error. */ - void error(const QString &message, ErrorCode error); + void error(const QString &message, Downloader::ErrorCode error); }; #endif diff -r cbd57d767dfa -r 62cd56cea09b ui/downloader_linux.cpp --- a/ui/downloader_linux.cpp Tue Mar 11 19:00:35 2014 +0100 +++ b/ui/downloader_linux.cpp Wed Mar 12 16:15:52 2014 +0100 @@ -7,7 +7,5 @@ #include "downloader.h" #ifdef Q_OS_LINUX -void Downloader::run() { -} #endif diff -r cbd57d767dfa -r 62cd56cea09b ui/downloader_win.cpp --- a/ui/downloader_win.cpp Tue Mar 11 19:00:35 2014 +0100 +++ b/ui/downloader_win.cpp Wed Mar 12 16:15:52 2014 +0100 @@ -27,6 +27,44 @@ #define MAX_SW_SIZE 10485760 #define MAX_LIST_SIZE 1048576 +/** @brief Download a file from the Internet + * + * @param[in] HSession the session to work in. + * @param[in] HConnect the connection to use. + * @param[in] resource the resource to download. + * @param[in] filename where the file should be saved. + * @param[in] maxSize maximum amount of bytes to download + * + * @returns True if the download was successful. + */ +bool downloadFile(HINTERNET hSession, HINTERNET hConnect, + LPCWSTR resource, const QString &filename, DWORD maxSize); + +/** @brief get the last modified header of a resource. + * + * On error call getLastError to get extended error information. + * This function still does not do any networking but only initializes + * it. + * + * @param[in] HSession the session to work in. + * @param[in] HConnect the connection to use. + * @param[in] resource the resource to check the last-modified date on + * + * @returns the last modified date or a null datetime in case of errors + */ +QDateTime getLastModifiedHeader(HINTERNET hSession, + HINTERNET hConnect, LPCWSTR resource); + +/** @brief verify that the certificate of the request matches + * + * Validates the certificate against the member variable certificate + * + * @param[in] hRequest: The request from which to get the certificate + * + * @returns True if the certificate exactly matches the one in hRequest + */ + +bool verifyCertificate(HINTERNET hRequest); #define LIST_RESOURCE "/incoming/aheinecke/test" diff -r cbd57d767dfa -r 62cd56cea09b ui/mainwindow.cpp --- a/ui/mainwindow.cpp Tue Mar 11 19:00:35 2014 +0100 +++ b/ui/mainwindow.cpp Wed Mar 12 16:15:52 2014 +0100 @@ -20,6 +20,7 @@ MainWindow::MainWindow() { createActions(); createTrayIcon(); + qRegisterMetaType("Downloader::ErrorCode"); connect(mTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); @@ -123,7 +124,7 @@ QDateTime listInstalledLastMod = mSettings.value("List/installedDate").toDateTime(); QDateTime swInstalledLastMod = mSettings.value("Software/installedDate").toDateTime(); - Downloader* downloader = new Downloader(this, QString::fromLatin1("www.files.kolab.org"), + Downloader* downloader = new Downloader(this, QString::fromLatin1("https://files.kolab.org"), QByteArray(), swInstalledLastMod, listInstalledLastMod); connect(downloader, SIGNAL(newListAvailable(const QString&, const QDateTime&)), @@ -139,8 +140,8 @@ void MainWindow::downloaderError(const QString &message, Downloader::ErrorCode error) { - // TODO decide what to show when and how. mCurMessage = message; + showMessage(); }