andre@908: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik andre@908: * Software engineering by Intevation GmbH andre@908: * andre@908: * This file is Free Software under the GNU GPL (v>=2) andre@908: * and comes with ABSOLUTELY NO WARRANTY! andre@908: * See LICENSE.txt for details. andre@908: */ andre@908: andre@908: #include "sslconnection_curl.h" andre@1058: #include "logging.h" andre@999: andre@999: #include andre@910: #include andre@908: andre@908: SSLConnectionCurl::SSLConnectionCurl(const QString& url, andre@908: const QByteArray& certificate): andre@908: SSLConnection (url, certificate), andre@908: mCurl (NULL) andre@908: { andre@908: curl_global_init(CURL_GLOBAL_DEFAULT); andre@908: mCurl = curl_easy_init(); andre@908: andre@908: if (!mCurl) { andre@908: qDebug() << "Failed to initialize curl"; andre@908: return; andre@908: } andre@908: andre@999: #ifdef RELEASE_BUILD andre@908: if (curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYPEER, 1L) != CURLE_OK) { andre@999: #else andre@999: /* For testing we do not have to trust the issuer. This should not andre@999: * be dangerous as we pin the peer certificate directly. */ andre@999: if (curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYPEER, 0L) != CURLE_OK) { andre@999: #endif andre@999: /* Should be default anyway */ andre@999: qDebug() << "Setting verifypeer failed"; andre@999: return; andre@999: } andre@999: andre@999: #ifdef RELEASE_BUILD andre@999: if (curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYHOST, 1L) != CURLE_OK) { andre@999: #else andre@999: /* For testing we do not have to trust host. This should not andre@999: * be dangerous as we pin the peer certificate directly. */ andre@999: if (curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYHOST, 0L) != CURLE_OK) { andre@999: #endif andre@908: /* Should be default anyway */ andre@908: qDebug() << "Setting verifypeer failed"; andre@908: return; andre@908: } andre@908: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_ERRORBUFFER, mErrBuf) != CURLE_OK) { andre@910: qDebug() << "Setting errorbuf failed"; andre@910: return; andre@910: } andre@910: andre@999: #ifdef RELEASE_BUILD andre@999: if (curl_easy_setopt(mCurl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2) != CURLE_OK) { andre@999: qDebug() << "Setting ssl version failed."; andre@999: return; andre@999: } andre@999: #endif andre@999: andre@908: mCertFile.open(); andre@908: if (mCertFile.write(mPinnedCert) != mPinnedCert.size()) { andre@908: qDebug() << "Failed to write temporary certificate"; andre@908: return; andre@908: } andre@908: mCertFile.close(); andre@908: andre@908: if (curl_easy_setopt(mCurl, CURLOPT_CAINFO, andre@908: mCertFile.fileName().toUtf8().constData()) != CURLE_OK) { andre@999: qDebug() << "Failed to set ca certificate"; andre@908: return; andre@908: } andre@991: andre@999: /* If the build fails here maybe you probably forgot to apply the andre@999: * trustbridge patches to curl */ andre@999: if (curl_easy_setopt(mCurl, CURLOPT_PEERCERT, andre@999: mCertFile.fileName().toUtf8().constData()) != CURLE_OK) { andre@999: qDebug() << "Failed set peer certificate."; andre@999: return; andre@999: } andre@908: mInitialized = true; andre@1058: if (g_debug) { andre@1058: curl_easy_setopt(mCurl, CURLOPT_VERBOSE, 1L); andre@1058: } andre@908: } andre@908: andre@908: SSLConnectionCurl::~SSLConnectionCurl() { andre@908: if (mCurl) { andre@908: curl_easy_cleanup (mCurl); andre@908: } andre@908: if (mInitialized) { andre@908: mCertFile.close(); andre@908: } andre@908: curl_global_cleanup(); andre@908: } andre@908: andre@908: int SSLConnectionCurl::connect() { andre@910: CURLcode retval; andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_URL, mUrl.toEncoded().constData()) != CURLE_OK) { andre@910: qDebug() << "Failed to set URL"; andre@910: return -1; andre@910: } andre@910: andre@908: if (curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 1L) != CURLE_OK) { andre@908: qDebug() << "Failed to set connect only option"; andre@908: return -1; andre@908: } andre@910: retval = curl_easy_perform(mCurl); andre@910: if (retval != CURLE_OK) { andre@910: qDebug() << "Failed to connect: " << mErrBuf << " retval: " << retval; andre@910: if (retval == CURLE_PEER_FAILED_VERIFICATION) { andre@910: mErrorState = InvalidCertificate; andre@910: return -1; andre@910: } andre@999: if (retval == CURLE_SSL_CONNECT_ERROR) { andre@999: mErrorState = SSLHandshakeFailed; andre@999: return -1; andre@999: } andre@910: andre@908: mErrorState = NoConnection; andre@908: return -1; andre@908: } andre@908: mConnected = true; andre@908: return 0; andre@908: } andre@908: andre@991: /* Globally do this as we can't pass "this" (the ptr) to the c function */ andre@910: size_t ssl_curl_max_write, ssl_curl_written; andre@908: andre@910: size_t write_data(void *ptr, size_t size, size_t nmemb, andre@910: QSaveFile *fp) andre@910: { andre@910: qDebug() << "Writing size: " << size << " * " << nmemb; andre@910: if (ssl_curl_max_write < ssl_curl_written) { andre@910: qDebug() << "Aborting write. Too much data."; andre@910: return 0; andre@908: } andre@910: size_t written = fp->write((const char *)ptr, size * nmemb); andre@910: if (written != size * nmemb) { andre@910: qDebug() << "Failed to write data. Written: " << written andre@910: << " requested: " << size * nmemb; andre@910: return 0; andre@908: } andre@910: ssl_curl_written += written; andre@910: return written; andre@908: } andre@908: andre@1058: size_t debug_write(void *ptr, size_t size, size_t nmemb, andre@1058: void *unused) andre@1058: { andre@1058: Q_UNUSED(unused); andre@1058: andre@1058: qDebug() << QString::fromUtf8((const char *)ptr, size * nmemb); andre@1058: return size *nmemb; andre@1058: } andre@1058: andre@910: bool SSLConnectionCurl::downloadFile(const QString &resource, andre@910: const QString &fileName, andre@910: size_t maxSize) andre@908: { andre@910: ssl_curl_written = 0; andre@910: ssl_curl_max_write = maxSize; andre@991: QSaveFile outputFile(fileName); andre@910: // Open / Create the file to write to. andre@910: if (!outputFile.open(QIODevice::WriteOnly)) { andre@910: qDebug() << "Failed to open file"; andre@910: return false; andre@910: } andre@910: QUrl urlCopy = mUrl; andre@910: urlCopy.setPath(resource); andre@908: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_URL, urlCopy.toEncoded().constData()) != CURLE_OK) { andre@910: qDebug() << "Failed to set URL"; andre@910: return false; andre@910: } andre@908: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 0L) != CURLE_OK) { andre@910: qDebug() << "Failed to set connect"; andre@910: return false; andre@910: } andre@908: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_HEADER, 0L) != CURLE_OK) { andre@910: qDebug() << "Failed to set header"; andre@910: return false; andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_NOBODY, 0L) != CURLE_OK) { andre@910: qDebug() << "Failed to set no body"; andre@910: return false; andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, write_data) != CURLE_OK) { andre@910: qDebug() << "Failed to set write function"; andre@910: return false; andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, &outputFile) != CURLE_OK) { andre@910: qDebug() << "Failed to set write function"; andre@910: return false; andre@910: } andre@910: andre@910: if (curl_easy_perform (mCurl) != CURLE_OK) { andre@910: qDebug() << "Failed to perform download."; andre@910: return false; andre@910: } andre@910: andre@910: if (!outputFile.commit()) { andre@910: qDebug() << "Failed to commit data to filesystem."; andre@910: return false; andre@910: } andre@910: andre@910: return true; andre@908: } andre@910: andre@910: QDateTime SSLConnectionCurl::getLastModifiedHeader(const QString &resource) { andre@910: QUrl urlCopy = mUrl; andre@910: urlCopy.setPath(resource); andre@910: andre@1058: if (curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, debug_write) != CURLE_OK) { andre@1058: qDebug() << "Failed to set write function"; andre@1058: return QDateTime(); andre@1058: } andre@1058: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_URL, urlCopy.toEncoded().constData()) != CURLE_OK) { andre@910: qDebug() << "Failed to set URL"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 0L) != CURLE_OK) { andre@910: qDebug() << "Failed to set connect"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_HEADER, 1L) != CURLE_OK) { andre@910: qDebug() << "Failed to set header"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_NOBODY, 1L) != CURLE_OK) { andre@910: qDebug() << "Failed to set no body"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_FILETIME, 1L) != CURLE_OK) { andre@910: qDebug() << "Failed to set filetime"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (curl_easy_perform (mCurl) != CURLE_OK) { andre@910: qDebug() << "Failed to perform last modified check."; andre@910: return QDateTime(); andre@910: } andre@910: long filetime = 0; andre@910: andre@910: if (curl_easy_getinfo (mCurl, CURLINFO_FILETIME, &filetime) != CURLE_OK) { andre@910: qDebug() << "Failed to get filetime"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (filetime == -1) { andre@910: qDebug() << "Invalid Time"; andre@910: return QDateTime(); andre@910: } andre@910: return QDateTime::fromTime_t(filetime); andre@910: } andre@956: andre@956: void SSLConnectionCurl::setProxy(const QUrl& proxyUrl) { andre@956: if (curl_easy_setopt(mCurl, CURLOPT_PROXY, proxyUrl.toEncoded().constData()) != CURLE_OK) { andre@956: qDebug() << "Failed to set proxy"; andre@956: return; andre@956: } andre@956: } andre@991: andre@991: void SSLConnectionCurl::setCiphersuites(int ciphers[]) { andre@999: QStringList cipher_list; andre@999: for (int i = 0; ciphers[i] != 0; i++) { andre@999: cipher_list << ssl_get_ciphersuite_name(ciphers[i]); andre@999: } andre@999: andre@999: if (curl_easy_setopt(mCurl, CURLOPT_SSL_CIPHER_LIST, andre@999: cipher_list.join(":").toLatin1().constData()) != CURLE_OK) { andre@999: qDebug() << "Failed to set cipher list"; andre@999: return; andre@999: } andre@991: }